1、定时器
1.1 概念
类似闹钟,达到一个设定的时间后,就执行指定好的代码
- 标准库中提供了一个 Timer 类,Timer类的核心方法 schedule
- schedule 方法包含两个参数
- 第一个参数:指定即将执行的任务代码(本质 Runnable )
- 第二个参数:制定多长时间之后执行
public static void main(String[] args) {
Timer t1 = new Timer();
t1.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("到点了 执行");
}
},3000);
System.out.println("时间快到了 ");
}
- TimerTask 的本质是 Runnable
- 里面的方法 靠Timer内部的线程,在时间到了才执行
- Timer 内置了线程(前台线程),会组织进程结束
- 想结束可以调用 cancel() 方法,如果还有正在执行的定时任务,会等到任务结束才会退出程序
- 如果想终止定时任务直接退出,可以purge()来立即清除所有定时任务
1.2 定时器的实现
定时器,内部管理的不只有一个任务,可以管理多个;
- 知道任务的优先级:一个带优先级的阻塞队列(堆)
- 队列的每个元素 是一个 Task 任务
- 每个任务的元素
- 任务的具体执行方法
- 任务执行的具体时间
1.2.1 任务 - Task
- 任务类,本质就是一个可以根据时间比较的Runnable类
- System.currentTimeMillis() 获取当前的时间戳
- delay等待的时间,time就是具体的时间戳
- 实现Comparable,对时间进行大小比较,因为需要放入优先级队列
- 可以将人物类作为Timer的内部类
class MyTask implements Comparable<MyTask>{
//任务 时间
public Runnable runnable ;
public long time; //用绝对时间戳,方便后续判定
//构造
public MyTask(Runnable runnable,long delay){
this.runnable = runnable;
//取当前的时间戳 + delay = 实际任务执行的时间戳
this.time = System.currentTimeMillis()+delay;
}
//时间比较
@Override
public int compareTo(MyTask o) {
//比较 建立小堆,取出的是时间最小的
return (int)(this.time - o.time);
}
}
1.2.2 定时
1. 通过优先级阻塞队列 存储若干个 Task类任务,通过schedule来往队列插入
//核心数据结构 优先级的阻塞队列
private PriorityBlockingQueue<MyTask> bqueue = new PriorityBlockingQueue<>();
//锁对象
private Object locker = new Object();
2. 核心接口方法 schedule,用于注册任务,放入优先阻塞队列
//schedule 方法 delay 是表示 毫秒的时间 (delay时间后执行任务)
public void schedule(Runnable runnable,long delay) throws InterruptedException {
//构建任务
MyTask task = new MyTask(runnable,delay);
//插入队列
bqueue.put(task);
synchronized (locker){
//唤醒等待
locker.notify();
};
}
3. Timer里有一个内置线程,在构造函数,对时间是否到进行判定是否执行时间;没到进行计算还需等待多少时间, 进入等待时间,避免进行循环忙等问题
//构造函数
public MyTimer(){
//构造线程
Thread thread = new Thread(()->{
while (true){
try {
//1.取出队头元素
MyTask myTask = bqueue.take();
//2.取到当前时间
long newTime = System.currentTimeMillis();
//3.判断时间是否到了
if(myTask.time <= newTime){
//4.时间到了执行任务
myTask.runnable.run();
}else {
//5.时间没到,放回队列
//问题:要确定 时间没到 不要忙等,循环取放
bqueue.put(myTask);
//等待相差时间
synchronized (locker) {
locker.wait( myTask.time-newTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
}
这几步写完了,但是没有问题吗,线程是否安全?加锁的位置正确吗?还能改进吗?
问题:14:30进入等待之前,14:10唤醒空大,14:30进入等待,14:10未能将线程唤醒,不会进入新的等待,直到等到14:30,;
解决:将时间判定执行的过程一块加锁,保证原子性,上一个未进入等待,新线程不会唤醒
public MyTimer(){
//构造线程
Thread thread = new Thread(()->{
while (true){
try {
//等待 要 加锁
synchronized (locker) {
//1.取出队头元素
MyTask myTask = bqueue.take();
//2.取到当前时间
long newTime = System.currentTimeMillis();
//3.判断时间是否到了
if(myTask.time <= newTime){
//4.时间到了执行任务
myTask.runnable.run();
}else {
//5.时间没到,放回队列
//问题:要确定 时间没到 不要忙等,循环取放
bqueue.put(myTask);
//等待相差时间
locker.wait( myTask.time-newTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
}
1.3.3 测试代码
public static void main(String[] args) throws InterruptedException {
MyTimer myTimer = new MyTimer();
//安排任务
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("倒计时:1");
}
},5000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("倒计时:2");
}
},4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("倒计时:3");
}
},3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("倒计时:4");
}
},2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("倒计时:5");
}
},1000);
System.out.println("火箭发射倒计时:");
}
2、线程池
2.1 概念
线程池是什么?
- 线程池是一种线程管理机制
- 主要作用:减少线程创建和销毁的开销,避免频繁创建和销毁线程所带来的性能损失和资源浪费;
- 在java中,线程池是由ThreadPoolExecutor类实现的;
为什么使用线程池?
从线程池里拿线程比从系统创建线程更高效
- 从线程池拿线程,是纯粹用户态操作
- 从系统创建线程,涉及到用户态和内核之间的切换,真正的创建是在内核完成的
内核态:操作系统运行时所处权限下的运行模式,此时操作系统内核具备有完全的控制权,可以访问系统的所有资源;
在系统调用时进程需要从用户态切换为用户态,以便访问内核下的系统资源和执行特权操作,这种切换是有一定的时间和开销;
操作系统 = 内核 + 配套的应用程序
应用程序同一时间有很多,交给操作系统创建,可能不是很及时,具体由系统决定
(就如去窗口提交(操作系统)办证件,什么办下来是工作人员(CPU)决定,切换为工作人员才能使用窗口内的设备,时间不可控)
用户态:进程运行时所处的普通权限下的运行模式,此时进程只能访问自己的用户空间,不能直接访问系统的;
用户在要开开辟的进程中,设定好要开辟线程,再提交给系统开辟进程(批准资源)
(证件资料什么用户已经准备好,只需要工作人员给盖章批准,时间可控)
2.2 线程池的创建
2.2.1 标准池中的线程池
java线程池是基于Executor框架实现的,能够提供高效的线程池管理和任务执行功能
Executor中创建线程池的方式:
- newFixedThreadPool(): 创建固定线程数的线程池
- newCachedThreadPool(): 创建线程数目动态增长的线程池.
- newSingleThreadExecutor(): 创建只包含单个线程的线程池.
- newScheduledThreadPool(): 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
public static void main(String[] args) {
//固定数目
ExecutorService pool1 = Executors.newFixedThreadPool(10);
//动态增长
ExecutorService pool2 = Executors.newCachedThreadPool();
//单个线程
ExecutorService pool3 = Executors.newSingleThreadExecutor();
//延迟执行
ExecutorService pool4 = Executors.newScheduledThreadPool(1000);
//提交任务到线程池
pool1.submit(new Runnable() {
@Override
public void run() {
System.out.println("注册任务到线程池");
}
});
}
- 通过 Executor 类将线程池构建出来
- Executors 本质上是 ThreadPoolExecutor 类的封装.
- 线程池返回值类型:ExecutorService;这是Executor框架提供的一个接口,用于管理和执行线程池中的人物
2.2.2 工厂模式
上述创建线程池的方法,采用的工厂模式,这是对构造方法不足的补充
例如,不同构造方法类型、数量、顺序要不一样,但是都一样,就构造方法执行不一样怎么办;
构造坐标系,有xy轴坐标系,也有极坐标坐标系,两个参数都一样,但是要区分;
工厂模式就是创建对象的过程中,通过将对象的创建过程封装在一个工厂类中,使得对象的创建过程更加灵活和可控。
2.2.3 构造方法参数
线程池的类、方法都在java.util.concurrent 这个包里,简称JUC (并发)
这是ThreadPoolExecutor的其中参数最全的一个构造方法,参数的含义:
1. corePoolSize (int):核心线程数(基本大小>=0) —— 正式员工
2. maximumPoolSize (int):最大线程数(线程池大小>=1) —— 正式+实习
线程数的多少取决于系统许运行的任务,多就加些实习,不多就不加,辞退实习
3. keepAliveTime (long):线程存活的时间(>=0)
4. unit :存活时间的单位(s、ms)
5. workQueue :线程池的任务是有阻塞队列来组织的(不能为空)
6. threadFactory:工厂模式,创建线程的辅助的类
7. handler:线程池的拒绝策略(池子满了,继续添加任务,如何拒绝)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
2.2.4 线程池拒绝策略
线程池的拒绝策略是当前线程池的任务满了(阻塞队列满了),并且线程池中的线程数达到最大值,新的任务无法继续提交时,线程池采用的处理方式。
Java标准库中的ThreadPoolExecutor类提供了四种线程池拒绝策略:
拒绝策略 | 功能 |
AbortPolicy | 默认的拒绝策略,当任务无法处理,直接抛出RejectedExecutionException异常 |
CallerRunsPolicy | 任务无法处理,在调用者的线程中直接执行该任务 |
DiscardOldestPolicy | 丢弃最老的任务(队头),尝试将新任务添加到队列 |
DiscardPolicy | 丢弃最新的任务(队尾),直接丢弃该任务,不做处理 |
class MyThreadPool {
//阻塞队列 存储任务
private BlockingQueue<Runnable> bq = new LinkedBlockingQueue<>();
//sumbit 提交任务
public void sumbit(Runnable runnable) throws InterruptedException {
//添加任务
bq.put(runnable);
}
//线程池构造、调度
public MyThreadPool(int n) {
//最多 n 个线程加入
for (int i = 0; i < n; i++) {
Thread thread = new Thread(() -> {
//循环取取任务
try {
while (true) {
Runnable runnable = bq.take();
//运行
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
}
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int num = i;
myThreadPool.sumbit(()->{
System.out.println("执行:"+num);
});
}
}
}