1.为什么要用线程池?
降低系统资源消耗
提高线程可控性
2.如何创建线程池?
JDK8中提供了5种创建线程池的方法
(1). newFixThreadPool
创建固定大小的线程池,可控制线程最大并发数,超过的线程会在队列中等待
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(2).(JDK8新增) newWorkStealingPool
会根据所需的并发数来动态创建和关闭线程,能够合理的使用CPU进行对任务进行并发操作,所以很适合使用在很耗时的任务上
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
这里返回的是一个ForkJoinPool对象
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode) {
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
使用一个无限队列来保存需要执行的任务,可以传入线程的数量,不传入的话,默认使用当前计算机中可以使用的CPU数量,使用分治法来解决问题,使用fork()和join()来进行调用
(3).newCashedThreadPool
创建一个可缓存的线程池,可以灵活回收空闲线程,如果无法回收,则新建线程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(4).newSingleThreadPoolExecutor
创建一个单线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(5).newScheduledThreadPool
创建一个定长线程池,支持定时以及周期性任务执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
3.源码结构(Executor框架结构)
从上往下依次是:
Executor
一个运行新任务的简单接口
ExecutorService
扩展了Executor接口,添加了一些用来管理执行器生命周期和任务生命周期的方法
AbstractExecutorService
对ExecutorService接口的抽象类实现
ThreadPoolExecutor
Java线程池的核心实现
4.线程池状态的转换模型
running:
线程池一旦被创建,就处于running状态,能够接收新任务,以及对已添加的任务进行处理
shutdown:
不能接收新任务,但能够处理已添加的任务
stop:
不接受新任务,不处理已添加的任务,并且会中断正在处理的任务
tidying:
当所有的任务已经终止,线程池就会变成tidying状态
terminated:
线程池彻底终止,就会变成terminated状态