目录
Day 12:多线程(10)
线程池
1. 标准库线程池(ThreadPoolExecutor)
构造方法比较复杂
(1)核心线程数/最大线程数:管理策略
(2)存活时间/时间单位:非核心线程允许空闲的最大时间
(3)任务队列BlockingQueue:存放了线程池要执行的任务
(4)线程工厂:辅助创建线程的工厂类(工厂模式)
(5)拒绝策略:任务队列满了,接下来继续submit,线程池要处理
2. 拒绝策略
理解过程和效果
- 直接抛出异常
- 由调用者负责执行:submit内部要做的事情不仅仅是入队列,如果发现队列满了并且使用当前这个策略,就会在submit内部自己去执行Runnable的run方法
- 丢弃最早的任务:任务队列中队首的元素舍弃掉
- 丢弃最新的任务:正在submit这个任务就不要了
实现拒绝策略的核心,在submit这里
- 构造方法里提供参数,用参数来表示哪种拒绝策略,使用成员变量记录一下
- submit的时候,先判定当前任务队列元素是否已经比较多了(自行设定一个阈值)
- 如果确实比较多了,就根据刚才构造时指定的拒绝策略,执行不同的逻辑
3. 标准库提供的创建线程池的工厂方法
由于标准库ThreadPoolExecutor使用起来比较费劲,于是标准库提供了几个工厂类,对于上述线程池又进一步封装了
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo34 {
public static void main(String[] args) {
//创建固定线程数目的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10000; i++) {
int id = i;
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + id + "," + Thread.currentThread().getName());
}
});
}
//创建一个最普通的线程池,能够根据任务的数目,自动进行线程扩容
Executors.newCachedThreadPool();
//创建一个只包含单个线程的线程池
Executors.newSingleThreadExecutor();
//创建一个固定线程个数,但是任务延时执行的线程池
Executors.newScheduledThreadPool(4);
}
}
当使用线程池的时候,main执行结束,进程没有结束,主要是因为线程池中的线程是前台线程,会阻止进程结束
4. 线程池实现
一个线程池要包含什么
- 有若干个线程
- 有任务队列(使用Runnable即可)
- 提供submit方法
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// 写一个比较简单的线程池.
class MyThreadPool {
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
private int maxPoolSize = 0;
private List<Thread> threadList = new ArrayList<>();
// 初始化线程池 (FixedThreadPool)
public MyThreadPool(int corePoolSize, int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
// 创建若干个线程
for (int i = 0; i < corePoolSize; i++) {
Thread t = new Thread(() -> {
try {
while (true) {
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
threadList.add(t);
}
}
// 把任务添加到线程池中
void submit(Runnable runnable) throws InterruptedException {
// 此处进行判定, 判定说当前任务队列的元素个数, 是否比较长.
// 如果队列元素比较长, 说明已有的线程, 不太能处理过来了. 创建新的线程即可.
// 如果队列不是很长, 没必要创建新的线程.
queue.put(runnable);
// 这里的 阈值 都是咱们拍脑门想的.
if (queue.size() >= 500 && threadList.size() < maxPoolSize) {
// 创建新的线程即可
Thread t = new Thread(() -> {
try {
while (true) {
Runnable task = queue.take();
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class Demo35 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool threadPool = new MyThreadPool(10, 20);
for (int i = 0; i < 10000; i++) {
int id = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + id + ", " + Thread.currentThread().getName());
}
});
}
}
}
上述代码并没有包含回收非核心线程,回收非核心线程需要引入更多的数据结构,也需要引入“定时器”概念
定时器
定时器:就是闹钟的效果,指定一个任务(Runnable),并且指定一个时间(3000ms),此时这个任务不会立即执行,而是在时间到达之后,再去执行,也就是所谓的定时执行/延时执行
定时器是日常开发中非常重要的基础组件,例如短信验证,验证码是有时效的,这样的效果就可以使用定时器,发送短信验证码的时候,生成验证码,保存起来,设定定时器,就会在5分钟之后,执行一个任务,删除刚才这个验证码
正因为定时器非常重要,未来实际开发中,甚至会把定时器功能单独封装成服务器,供整个分布式系统来使用
1. Timer
Timer是Java标准库中的定时器
package thread;
import java.util.Timer;
import java.util.TimerTask;
public class Demo36 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 2000");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 1000");
}
},1000);
}
}
timer.schedule
:把任务在合理的时间安排好TimerTask
:schedule的第一个参数,是一个抽象类,继承了Runnabledelay
:schedule的第二个参数,表示“多长时间后”执行,以当前执行schedule的时刻为基准,继续等delay时间之后再去进一步执行- Timer内部包含了前台线程,阻止了进程结束
- Timer里面内部包含了线程,把线程创建都封装起来了
2. Timer代码实现
2.1 代码需求
- 能够延时执行任务/指定时间执行任务
- 能够管理多个任务
2.2 代码实现
- 定义一个MyTimerTask类,包含任务(Runnable)与延时时间(delay)
- 首先,该类的成员变量包含
Runnable runnable
与long time
,time
为绝对时间,等于系统当前时间加上delay - 其次,构造方法中,构造上述两个成员变量
- 然后,成员方法提供两个:
void run()
用于执行runnable任务;int getTime()
返回time
该任务执行的时间 - 最后,重写
compareTo
,为后续优先级队列提供比较对象
- 首先,该类的成员变量包含
- 定义一个MyTimer类
- 首先,定义一个优先级队列,元素类型MyTimerTask类,通过优先级队列将这些任务保存起来,按照时间作为优先级的先后标准,可以做到,队首元素就是时间最靠前的任务
- 然后,在构造方法中,定义一个线程,用于循环扫描上述队列,扫描线程只需要关注队首元素即可,时间最靠前的任务都还没有到点,剩下的任务更不会到点了
- 最后,定义成员方法
void schedule(Runnable runnable, long delay)
,第一个参数表示任务,第二个参数表示延时时间,将该任务保存到优先级队列当中
【注意】
(1)扫描线程中,获取到队首元素,判定是否到时间,到时间就执行,并且出队列,没到时间,就继续下一次循环
(2)引入锁,把针对队列的操作,都加锁了
(3)解决忙等问题,引入wait和notify:队列为空要wait(死等);队首元素没到时间,wait(带有超时时间);添加了新的元素到队列中后,需要notify
这里不能使用sleep:sleep通过interrupt唤醒是非常规手段;sleep不会释放锁,会影响后续插入任务
(4)引入比较规则,让MyTimerTask可以按照时间先后来制定优先级
package thread;
import java.util.PriorityQueue;
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable;
//什么时候去执行,此处的time不是delay,而是ms级别的时间戳(绝对时间)
private long time;
public MyTimerTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTimerTask o) {
//按照时间来进行比较,期望最终构造出小根堆
return (int)(this.time - o.time);
}
}
class MyTimer {
private PriorityQueue<MyTimerTask> taskQueue = new PriorityQueue<>();
private Object locker = new Object();
public MyTimer() {
//这个线程负责不停的扫描上述的队列队首元素,来确定是否要执行任务
Thread t = new Thread(()->{
try {
while (true) {
synchronized (locker){
if (taskQueue.size() == 0){
locker.wait();
}
MyTimerTask task = taskQueue.peek();
long curTime = System.currentTimeMillis();
if (curTime >= task.getTime()) {
//时间到了,要执行任务
task.run();
taskQueue.poll();
}else {
//时间没到
locker.wait(task.getTime() - curTime);
}
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
});
t.start();
}
public void schedule (Runnable runnable, long delay) {
synchronized (locker){
MyTimerTask task = new MyTimerTask(runnable, delay);
taskQueue.offer(task);
locker.notify();
}
}
}
public class Demo37 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 2000");
}
},2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 1000");
}
},1000);
}
}
上述是一种典型的定时器的实现,除了这种基于优先级队列之外,还有一种典型的实现,基于“时间轮“的方式。
时间轮反而更简单一点,类似于带环的链表,每个节点表示一个时间单位,每个节点上再管理一系列的任务,有一个线程每隔固定时间,遍历到下一个节点