java 线程池存在意义
很多刚接接触线程池的人会很纳闷,我明明可以用new Thread().start() 启动我想要的线程,那么为什么还多此一举,用线程池呢?
首先要明白程序最重要运行到一台机器上的,那么对于一台机器来说, 他的CPU,IO,网络,DB等资源是有限的,因此我们用的时候不能够随意支取,而是要有一个中介来统一接收请求,然后中介去帮忙对资源进行控制,调度,回收(数据库连接池也是一个道理,为了防止程序频繁无休止的创建Connection把数据库拖挂,所以用了连接池)。
线程对于一台机器来说是稀缺资源, 因此程序里面不能随意的new Thread,而应该通过创建一个全局Globa的 Thread Pool,然后大家如果想要申请使用Thread资源的话,统一给线程池下发任务,然后线程池里面有一个Task Queue缓存任务,然后线程池根据配置,结合当前机器的运行状况,开线程从Task QUeue中消费Task运行,这样防止把机器的线程资源耗尽
线程的生老病死都是要耗费性能的,所以最好不要频繁的创建回收Thread,浪费机器性能。因此用一个线程池,一直开着一定数量的线程,当有Task来了,就取线程消费Task Queue
解耦, 调用方只用创建Task,然后传给线程池,线程池缓存Task,然后线程池中的Thread消费Task Queue,这样达到Task创建和执行的解耦。
java如何创建线程池
比较常用的 用Executors的各种静态方法创建需要的线程池
ExecutorService cachedthreadPool = Executors.newCachedThreadPool();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
ExecutorService SingleThreadPool = Executors.newSingleThreadExecutor();
其实Executors的四种方法底层都是用 new ThreadPoolExecutor 来创建线程池的,因此为了搞清楚上面四种方法的区别,我们还是深入研究一下ThreadPoolExecutor的原理,为了研究ThreadPoolExector的原理我们先画图讲解JAVA 线程池的原理
Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
可以看到: newCachedThreadPool结果会创建一个最小0 -- Integer.MAX_VALUE个线程, 使用SynchronousQueue接受Task,如果队列满了,等待 60S,如果等待超时那么调用默认的defaultHandler也就是new AbortPolicy()
Executors.newFixedThreadPool()
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* nThreads threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
可以看到: newFixedThreadPool 结果会创建一个最小0 -- nThreads个线程, 使用 LinkedBlockingQueue 接受Task,如果队列满了,不等待,直接调用默认的defaultHandler也就是new AbortPolicy()
Executors.newScheduledThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
其中的 ScheduledThreadPoolExecutor 如下
/**
* Creates a new {@code ScheduledThreadPoolExecutor} with the
* given core pool size.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
比较推荐的创建线程池方法是
因为Executors底层还是调用new ThreadPoolExecutor创建线程池,因此在平时使用的时候还是比较推荐直接使用ThreadPoolExecutor创建
java 线程池原理和处理流程
如果所示
创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), new ArrayBlockingQueue(5), rejectedExecutionHandler)
【调用方提交Task】 调用 threadPoolExecutor.execute(task)或者threadPoolExecutor.submit(task)给线程池提交task
【threadPoolExecutor把task防区WorkQueue队列中】,等待线程池中Thread消费Task
【判断coreThreadPool线程额数是否达到最大】如果没有,且WorkQueue有堆积的Task那么,多开线程消费Task
【如果线程已经达到最大值,且WorkQueue已经满了,且又有新的Task来了,那么触发饱和策略】rejectedExecutionHandler
常见的饱和策略: 1) 直接丢弃; 2)调用发自己写rejectedExecutionHandler处理这些Task; 3)丢弃队列中的最近任务
合理配置线程池内core thread个数
线程池中的线程个数不是越大越好,要根据任务分类来的。
IO密集型任务: 线程个数增大,可以增加系统的吞吐。比如可以配置线程个数为 CPU线程数的2倍
计算密集型任务: 为了避免频繁切换CPU线程上下文,所以线程个数应该跟根据CPU核数配置,CPU线程数的0.8倍。比如CPU是2核4线程的,那么计算密集型的话,可以配置线程最大个数为 4 * 0.8 = 3个
混合型任务: 可以结合上面两类综合考虑配置
一些外面文章说的不恰当的论述
1. 线程池不用的时候为什么要调用shutDown() ?
网上一贯回答: 我们是为了不让线程池再新加task。
个人理解
为了解答这个问题,直接看源码最直接,省的弯弯绕绕的。
public void shutdown(){
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//核心地方在这里
tryTerminate();
}//这个会一直轮训, 直到所有task执行完,然后销毁线程池的线程,关闭线程池
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
综上:
主要目的是当线程池不用的时候, 销毁线程池维持的线程,关闭线程池