前言:
本文将介绍线程中使用的定时器与线程池,定时器就像一个闹钟,在定好的时间到达后便开始执行代码。线程池类似于我们所熟悉的字符串常量池、数据库连接池等等,使用线程池能更加高效的创建线程和销毁线程。
一、定时器
1.定时器是什么
2.标准库内的定时器
标准库中提供了一个 Timer 类 . Timer 类的核心方法为 schedule .schedule 包含两个参数 . 第一个参数指定即将要执行的任务代码 , 第二个参数指定多长时间之后执行 ( 单位为毫秒 )
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
3.实现定时器
定时器的构成:
- 一个带优先级的阻塞队列
- 为啥要带优先级呢?
- 因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.
- 队列中的每个元素是一个 Task 对象.
- Task 中带有一个时间属性, 队首元素就是即将
- 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
实现代码(内附部分代码说明):
/**
* 定时器的构成:
* 一个带优先级的阻塞队列
* 队列中的每个元素是一个 Task 对象.
* Task 中带有一个时间属性, 队首元素就是即将
* 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
*/
public class MyTimerDemo {
//Task 类用于描述一个任务(作为 Timer 的内部类).
//里面包含一个 Runnable 对象和一个 time(毫秒时间戳)这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.
static class Task implements Comparable<Task>{
private Runnable command;
private long time;
public Task(Runnable command , long time){
this.command = command;
this.time = System.currentTimeMillis() + time;
}
public void run(){
command.run();
}
@Override
public int compareTo(Task o){
return (int)(this.time - o.time);
}
}
//通过 PriorityBlockingQueue 来组织若干个 Task 对象.
//通过 schedule 来往队列中插入一个个 Task 对象.
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
private Object checker = new Object();
//Worker类用于一直检查队列中最快到达启动时间的线程,但是需要设置锁来解决忙等问题,
class Worker extends Thread{
@Override
public void run(){
while(true){
try {
Task task = queue.take();
long currentTime = System.currentTimeMillis();
if(task.time > currentTime){
queue.put(task);
//设置锁解决忙等问题不然线程会一直在检查还没开始的线程
//就像你计划3点30学习,但是还没到3.30,你就一直在看时间,什么都没干,所以造成浪费宝贵的30分钟,你全用来检查有没有到钟了
synchronized(checker) {
checker.wait(task.time - currentTime);
}
}else{
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public MyTimerDemo(){
//创建定时器类时,将检查类实例化
Worker worker = new Worker();
//检查线程开始
worker.start();
}
//核心代码:schedule方法安排任务,并指定任务多久后执行
public void schedule(Runnable command , long after){
//参数after表示当前时间后过多长时间开始执行任务
Task task = new Task(command , after);
queue.offer(task);
//将新的线程加载进去
synchronized (checker){
// 唤醒正在等待的锁,然后重新检查最先开始的任务
checker.notify();
}
}
public static void main(String[] args) {
MyTimerDemo myTimerDemo = new MyTimerDemo();
Runnable command = new RunnableDemo(){
@Override
public void run(){
System.out.println("我来了");
myTimerDemo.schedule(this,3000);
}
};
myTimerDemo.schedule(command , 3000);
}
}
二、线程池
1.引入线程池
引入线程池就是想在进一步“强化”线程的有点,相比于进程的创建和销毁,线程的创建和销毁已经够轻量了,但是在更频繁的情况下,创建/销毁线程,系统也有点消耗不起。
所以想到了线程池这个办法,就是先创建好一定数量的线程放到线程池中,需要使用线程,就从线程池中取出已经创建好的线程来执行任务,结束任务就直接将线程返回池子中。
这个过程只要在创建线程池的时候需要和系统打交道,其他【创建】/ 【销毁】线程的时候,只需跑到线程池中【取出来】/【放回去】就好,只要就节省了创建销毁的消耗
2.为什么使用线程池效率更高
这牵扯到一个CPU用户态和内核态的问题。
- 【用户态】运行用户程序
- 【内核态】会调用到操作系统内核的程序,操纵硬件
通常认为【用户态】比【内核态】的效率更高,举一个简单的例子:
👆面这个例子中,将打印任务交给自己就是以【用户态】的状态去解决问题,可以立马得到解决,而将任务交给前台,就是以【内核态】的状态去完成任务,但是这样你不知【内核态】什么时候才能解决问题,可能快可能慢。所以通常情况下,我们都任务【用户态】效率会比【内核态】的高。
基于此,我们使用【线程池】的原因也就有迹可循了,每次的从线程池中获取/销毁线程都是一个【用户态】可以马上解决燃眉之急,而直接让系统创建,说不定系统在忙别的事情,比较CPU核数就这么多,而且任务还不少,指不定什么时候才给你创建。
线程池最大的好处就是减少每次启动、销毁线程的损耗
3.标准库内的线程池
- 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
- 返回值类型为 ExecutorService
- 通过 ExecutorService.submit 可以注册一个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors 创建线程池的几种方式
- newFixedThreadPool: 创建固定线程数的线程池
- newCachedThreadPool: 创建线程数目动态增长的线程池.
- newSingleThreadExecutor: 创建只包含单个线程的线程池.
- newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
Executors 本质上是 ThreadPoolExecutor 类的封装 .ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定.
4.实现线程池
- 核心操作为 submit, 将任务加入线程池中
- 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
- 使用一个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
- 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了.
实现代码(内附代码说明):
public class MyThreadPool {
private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
private int maxThreadCounter;
//提交任务方法
public void submit(Runnable task){
try {
//将任务放到队列内部
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public MyThreadPool(int maxThreadCounter){
this.maxThreadCounter = maxThreadCounter;
for(int i = 0 ; i < this.maxThreadCounter ; i++){
//创建线程,将阻塞队列的引用作为参数
Worker worker = new Worker(queue);
worker.start();
}
}
public static void main(String[] args) {
//实现自定义线程池
MyThreadPool myThreadPool = new MyThreadPool(10);
for(int i = 0 ; i < 100 ; i++){
//往线程池中提交任务
myThreadPool.submit(() -> System.out.println("上号!!!"));
}
}
}
//执行线程
class Worker extends Thread{
//设置变量引用阻塞队列
private LinkedBlockingQueue<Runnable> queue;
public Worker(LinkedBlockingQueue<Runnable> queue){
super("worker");
this.queue = queue;
}
@Override
public void run(){
try {
//只要不执行中断,线程就从队列中取出任务来执行
while(!Thread.currentThread().isInterrupted()){
Runnable task = queue.take();
task.run();
}
} catch (InterruptedException e) {
}
}
}
个人笔记使用~~
感谢阅读