线程池
线程池的优势
降低系统资源消耗,可以重用已存在的线程,减少了线程创建和销毁的不必要消耗;
提高系统响应速度,当有任务到达时,通过重用已存在线程,可以无需等待新线程的创建;
方便管理线程并发数量;
还有一些扩展功能,如延时定时线程池
线程池的流程
)
基础的使用
主要通过Executors的newXXXThreadPool();/newXXXThreadExecutor();方法来创建线程池(返回ExecutorService对象,ExecutorServcice是Java提供的用于管理线程池的类),通过对象的execute方法调用线程池里的线程执行对应的代码。
// FixedThreadPool(n)代表创建一个内部有固定n个的线程的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// SingleThreadExecutor()代表创建一个只有一个线程的线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// CachedThreadPool()代表创建可以无限扩大的线程池
ExecutorService executor = Executors.newCachedThreadPool();
复制代码
newFixedThreadPool方法的源码:
newSingleThreadExecutor方法的源码:
newCachedThreadPool方法的源码:
使用的模板:
public void init(){
// 三种自带的线程池的创建方式
ExecutorService executor2 = Executors.newFixedThreadPool(5);
ExecutorService executor3 = Executors.newSingleThreadExecutor();
ExecutorService executor = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
// 用execute来使用线程池中的线程
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " thread running");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 最后需要关闭线程池
executor.shutdown();
}
}
复制代码
以上的三个方法最终都调用了下面的这个构造方法:
线程池中构造方法中的七大参数的用途
int corePoolSize :
表示线程池中常驻的核心线程数
int maximumPoolSize:
表示线程池中能容纳的同时执行的最大线程数,此值必须>=1
long keepAliveTime:
多余的空闲线程存活时间(线程池中线程数量超过corePoolSize,且空闲时间达到keepAliveTime,多余线程被销毁,到最后只剩下corePoolSize 的数量)
TimeUnit unit:
keepAliveTime的单位
BlockingQueue workQueue:
任务队列,存放被提交但是还未执行的任务
ThreadFactory threadFactory:
表示生产线程的线程工厂,一般默认即可
RejectedExecutionHandler handler:
定义拒绝策略,表示当队列满了,且工作线程数大于等于线程池的最大线程数以后如,何拒绝请求执行的runnable
为什么要使用阻塞队列,而不是非阻塞的?
阻塞队列在任务队列中没有任务时,阻塞获取任务的线程,使线程进入wait状态,释放CPU的资源。
当队列中有任务时才会唤醒对应的线程,从队列中取出消息并执行。
线程池底层工作的流程
线程池创建,等待请求
当调用execute()方法后,添加一个请求任务开始判断:
如果当前正在运行的线程数量小于corePoolSize,那么马上调用线程来执行这个任务;
如果当前正在运行的线程数量大于等于corePoolSize,且等待队列未满,那么将这个任务加入队列
如果此时队列已经满了,且正在运行的线程数量小于maximumPoolSize,那么扩容,即开启非核心线程来运行这个任务;
如果队列已满,且运行的线程数量大于等于maximumPoolSize,那么线程池回启动饱和拒绝策略来应对
当一个线程完成任务时,从等待队列中取出下一个任务来执行;
当一个线程空闲超过keepAliveTime,线程判断:
当运行的线程数大于corePoolSize,那么此时这个空闲线程被停止,否则不处理
最后线程池所有任务完成后,最终会收缩到corePoolSize的大小。
创建线程池的规范
根据阿里发布的开发手册:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
所以在实际开发时一般会自己创建线程池:
public static void main(String[] args){
ExecutorService threadPool =
new ThreadPoolExecutor(2,// 常驻核心线程数
Runtime.getRuntime().availableProcessors() + 1,// 最大线程数
2,// 空闲存活的时间
TimeUnit.SECONDS,// 空闲存活时间的单位
new LinkedBlockingQueue<>(3),// 等待队列,一般需要给定队列的大小
Executors.defaultThreadFactory(),// 线程工厂,一般使用默认的
new ThreadPoolExecutor.DiscardOldestPolicy());// 拒绝策略
try {
for (int i = 0; i < 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " thread running... ...");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
复制代码
注意点:
线程池的最大的线程数(maximumPoolSize)一般可以设置为本机的 逻辑处理器数量+1 ,而逻辑处理器的数量可以通过Runtime.getRuntime().availableProcessors()来获取;
存放等待任务的阻塞队列最好赋予初始值,如果使用默认情况,队列的长度会过大,这样拒绝策略相当于无效了,不可取;
线程工厂通过Executors.defaultThreadFactory()来创建默认工厂;
拒绝策略通过new ThreadPoolExecutor.xxxPolicy()来创建指定
四种拒绝策略:
均发生在当线程全部在使用且阻塞队列满时
1、AbortPolicy()
丢弃要进入的任务,并且抛出RejectedExecutionException异常。
2、CallerRunsPolicy()
交给直接调用此线程的线程来处理要进入的任务。
3、DiscardPolicy()
丢弃要进入的任务,但是不抛出异常,静默丢弃。
4、DiscardOldestPolicy()
丢弃阻塞队列中排在最前面的任务,然后重新提交被拒绝任务。