一句话攻略:3个方法,7种属性,4种策略 (来自b站up:狂神说Java)
使用Executors获得线程池, 3个方法
(不推荐使用,也是阿里开发手册不建议的方式)
-
Executors.newSingleThreadExecutor()
获取只有一个线程构成的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
-
Executors.newFixedThreadPool(int nThread)
获取指定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
-
Executors.newCachedThreadPool()
获取一个理论最大2^31-1的线程池,按需求可以一直增加线程。这也是不推荐使用的原因,计算机并不能承载理论大小的线程数量,会导致OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
简单观察可知以上方法均简单封装了new ThreadPoolExecutor()
,所以线程池的核心仍是ThreadPoolExecutor
。上述方法不被推荐是因为Executors进行创建提供的参数太少,没有进行控制的参数太多,不利于使用也不利于优化。
下面以ThreadPoolExecutor
为例分析线程池的运行原理
7个属性指ThreadPoolExecutor
共有7
个主要属性
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
从完整的构造方法来看
-
@param corePoolSize
线程池的核心线程数 -
@param maximumPoolSize
线程池的最大线程数 -
@param keepAlive
非核心线程不工作状态的最大存活时间 -
@param unit
keepAlive的时间单位 -
@param workQueue
线程池中任务排队的阻塞队列 -
@param handler
拒绝任务时的具体策略 -
@param threadFactory
使用的线程构造工厂(一般不改变)
对于理解线程池工作原理而言最重要的是 核心线程数,最大线程数,和不工作状态的存活时间。
此处应有图
简述运行过程:
- 通过
.execute(task)
方法添加任务到线程池,task
是一个实现run
方法的Runnable
对象 - 根据当前线程池的运行状态:
- 当前线程数不足核心线程数: 创建核心线程并将任务添加到Worker
- 当核心线程已满,而队列未满时。将任务添加到队列进行等待
- 当核心线程和等待队列都满时,如果线程数小于最大线程数,开启临时线程处理
- 临时线程在最大存活时间内都没有运行,就销毁线程
- 已达到最大线程数,队列已满,任务无法添加只能执行拒绝策略
4种策略
其实所指的是拒绝策略。
RejectedExecutionHandler的实现类
初始化传入的拒绝策略是RejectedExecutionHandler
,上述是全部实现类。
代码样例:
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(
2, //核心线程数
5, //最大线程数
1, //超时时长
TimeUnit.SECONDS, //时长单位
new ArrayBlockingQueue<>(3), //定长阻塞队列
new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略
for (int i = 1; i <= 10 ; i++) {
try {
service.execute(()->{
System.out.println(Thread.currentThread().getName()+"-------OK");
});
}catch (Exception e){
e.printStackTrace();
}
}
service.shutdown();
}
可以看到允许的策略分别是:
-
AbortPolicy
默认策略,当队列满时抛出java.util.concurrent.RejectedExecutionException
-
CallerRunsPolicy
谁调用谁执行(原本主线程将任务试图将任务添加到线程池执行,该策略拒绝后,由主线程自己执行任务) -
DiscardPolicy
抛弃当前任务 -
DiscardOldestPolicy
当前任务和队列中加入最早的任务竞争
调优相关
- CPU密集型
需要让CPU能充分运行,所以设置最大线程数>=CPU核数 - IO密集型
IO操作不占用CPU,但长时间占用线程资源。导致CPU空闲但没有线程可以用于执行任务。所以最大线程数可以设置为IO线程数的两倍