目录
3.1 使用 Executors 自动创建线程池(六种实现方法)
3.2 通过ThreadPoolExecutor 手动创建线程池(一种实现方法)※
4.1线程池的拒绝策略(4 [JDK 提供的拒绝策略] + 1 [自定义拒绝策略])
1、什么是线程池?
线程池就是用池化技术管理和使用线程的一种机制。
2、为什么要用线程池?
2.1 线程池的优点
①复用线程,从而避免了线程重复创建和销毁的性能开销。
②控制线程的数量,从而避免了因为线程创建过多而导致OOM的问题。
③提供了任务管理的功能,从而可以实现任务缓存和任务拒绝的功能。
④线程池提供了更多的功能,比如定时任务。
2.2 传统线程的缺点
①每次都需要创建和销毁线程,是需要消耗系统资源的。
②线程没有任务管理的功能,当任务量比较大的时候没有任务队列对任务进行管理或者是拒绝任务。
3、线程池的使用(两种实现方式+七种实现方法)
3.1 使用 Executors 自动创建线程池(六种实现方法)
①创建一个固定大小的线程池
特点:可控制并发编程的线程数,超出的线程会在等待队列中等待。
//1.创建了一个固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//2.使用线程池执行任务
for (int i = 0; i < 5; i++) {
//给线程池添加任务(使用线程池执行任务)
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程名称"+ Thread.currentThread().getName());
}
});
②创建一个可缓存的线程池
特点:若线程数超过处理所需,缓存一段时间后回收,若线程数无法满足任务数需要则创建新线程,适用于短时间有大量任务的场景,缺点是占用资源较多。
//2.创建一个带缓存的线程池
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
int finalI = i;
service.submit(()->{
System.out.println("i:"+ finalI +"线程名称:"+ Thread.currentThread().getName());
});
}
③创建执行定时任务的线程池
/**
* 创建执行定时任务的线程池
*/
public class ThreadPollDemo5 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务的时间:"+ LocalDateTime.now());
// //1.延迟两秒执行
// for (int i = 0; i < 5; i++) {
// service.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("执行任务:"+LocalDateTime.now()+Thread.currentThread().getName());
// }
// },2,TimeUnit.SECONDS);
// }
// //2. 2s 后开执行定时任务,定时任务时间间隔为4s
// service.scheduleAtFixedRate(new Runnable() {
// @Override
// public void run() {
// System.out.println("执行任务:"+LocalDateTime.now());
// try {
// Thread.sleep(5*1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// },2,4, TimeUnit.SECONDS);
//3.延迟两秒执行,定时任务时间间隔为4s
service.scheduleWithFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+LocalDateTime.now());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},2,4,TimeUnit.SECONDS);
}
}
※注意事项
区分At 与 With
At 任务执行时间间隔是以任务开始时间作为开始时间节点的,而 With 的时间间隔是以任务的结束时间作为开始时间节点的。
④单线程执行定时任务的线程池(③的单线程版本)
⑤单线程线程池
/**
* 单线程线程池
*/
public class ThreadPoolDemo6 {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.submit(()->{
System.out.println("执行任务:"+Thread.currentThread().getName());
});
}
}
就等价于①创建固定线程数线程池将参数设置为1
既然是单个线程的线程池,为何不直接使用线程?
还是线程池的存在的意义:它可以复用线程;并且单线程的线程池提供了任务队列和拒绝策略。(当任务队列满了之后,新来的任务就会被线程池自行拒绝)
⑥根据当前设备的配置自动生成线程池
3.2 通过ThreadPoolExecutor 手动创建线程池(一种实现方法)※
根据阿里编码公约:线程池不允许使用Executors去创建,而是需要通过手动的方式去创建,这样处理方式可以规避资源耗尽的风险。因为用Executors创建,有些默认的参数较大,可都会导致OOM。故引入了手动方式创建线程池的方法:
public class ThreadPoolDemo7 {
public static void main(String[] args) {
//创建线程工厂
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//thread.setName("樱桃老丸子"+r.hashCode());
thread.setPriority(Thread.MAX_PRIORITY);
return thread;
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), factory, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("My rejectExecution");
}
});
for (int i = 0; i < 100; i++) {
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"任务:"+ finalI);
}
});
}
}
}
4、线程池的执行流程
4.1线程池的拒绝策略(4 [JDK 提供的拒绝策略] + 1 [自定义拒绝策略])
JDK提供的拒绝策略:
自定义的拒绝策略:
4.2 线程池的状态
从上图可以看出线程池总共有五种状态,分别为:
①RUNNING:此时为正常的运行状态:接受新的任务,处理等待队列中的任务;
②SHUTDOWN:不接受新任务,但是会处理等待队列中的任务;
SHUTDOWNNOW:不接受新任务,也不会处理等待队列中的任务。
③STOP:不接受新的任务,也不再处理等待队列中的任务;
④TIDYING(收拾整理):该状态下所有任务都终止,将会执行terminated()方法;
⑤TERMINATED:线程池销毁状态。