一、线程池的优点和处理流程
1、优点
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
2、处理流程

二、扩展知识
1、corePoolSize和maximumPoolSize可以在创建之后使用setCorePoolSize()、setMaximumPoolSize()进行改变。
2、核心线程默认是第一个任务到达时创建的,但是可以通过prestartCoreThread()、prestartAllCoreThreads()覆盖进行提前创建(传入一个非空队列时可使用)。
3、默认的ThreadFactory使用相同的ThreadGroup、相同的NORM_PRIORITY和non-daemon status创建线程,你可以使用自定义线程工厂修改线程名称、组、优先级、守护状态。创建线程失败newThread()返回null时,执行器继续工作,但是可能不会执行任务。
4、超出corePoolSize的额外线程(即非核心线程)如果它们空闲时间超过了keepAliveTime之后会被终止。可以通过allowCoreThreadTimeOut(boolean)让核心线程也会被超时回收。
5、队列的策略通常三种
直接传递:SynchronousQueue(通常要求maximumPoolSizes无界避免新任务被拒绝)。
无界:LinkedBlockingQueue(maximumPoolSize没有效果,不会创建非核心线程)。
有界:ArrayBlockingQueue(大队列少线程可以最小化CPU使用率、OS资源和上下文切换,但是会降低吞吐量。如果任务频繁阻塞,可以分配更多线程。小队列多线程可以保持CPU繁忙但是会带来更多的调度开销,也可能降低吞吐量)。
6、拒绝策略
AbortPolicy:默认的,抛出RejectedExecutionException。
CallerRunsPolicy:调用execute()的线程执行,可以提供简单反馈控制但是减缓任务提交速度。
DiscardPolicy:丢弃任务。
DiscardOldestPolicy:先丢掉队列首部任务,再execute,再次失败时将导致重复。
7、钩子方法
beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable)会在每个任务execute之前和之后被调用,可以用来做一些监控或者日志记录等。钩子方法抛出异常可能导致线程失败或终止。
8、可以使用getQueue()获取任务队列然后remove/purge做一些任务回收管理。
9、程序不在引用并且没有存活线程的pool将会自动shutdown。
10、线程池运行状态(跟线程状态是两个概念哈)
RUNNING: Accept new tasks and process queued tasks
SHUTDOWN: Don't accept new tasks, but process queued tasks
STOP: Don't accept new tasks, don't process queued tasks, and interrupt in-progress tasks
TIDYING: All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
TERMINATED: terminated() has completed
11、线程池运行状态转换图(跟线程状态转换图是两个概念哈)

12、如果添加队列成功,判断当前池内线程数是否为0,如果是则创建一个firstTask为null的worker,这个worker会从等待队列中获取任务并执行。
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
// 注意这一行代码,添加到等待队列成功后,判断当前池内线程数是否为0,如果是则创建一个firstTask为null的worker,这个worker会从等待队列中获取任务并执行。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
13、核心线程(Worker)即使一直空闲也不终止,是通过workQueue.take()实现的,它会一直阻塞到从等待队列中取到新的任务。非核心线程空闲指定时间后终止是通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)实现的,一个空闲的Worker只等待keepAliveTime,如果还没有取到任务则循环终止,线程也就运行结束了。
14、线程池保持空闲的核心线程是它的默认配置,一般来讲是没有问题的,因为它占用的内存一般不大。怕的就是业务代码中使用ThreadLocal缓存的数据过大又不清理。
- ThreadLocal:业务代码是否使用了ThreadLocal?就算没有,Spring框架中也大量使用了ThreadLocal,你所在公司的框架可能也是一样。
- 局部变量:线程处于阻塞状态,肯定还有栈帧没有出栈,栈帧中有局部变量表,凡是被局部变量表引用的内存都不能回收。所以如果这个线程创建了比较大的局部变量,那么这一部分内存无法GC。
- TLAB机制:如果你的应用线程数处于高位,那么新的线程初始化可能因为Eden没有足够的空间分配TLAB而触发YoungGC。
15、在JDK1.8中,keepAliveTime=0表示非核心线程执行完立刻终止。
16、怎么进行异常处理
- 不论是用execute还是submit,都可以自己在业务代码上加try-catch进行异常处理。我一般喜欢使用这种方式,因为我喜欢对不同业务场景的异常进行差异化处理,至少打不一样的日志吧。
- 如果是execute,还可以自定义线程池,继承ThreadPoolExecutor并复写其afterExecute(Runnable r, Throwable t)方法。
- 或者实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory。
- 但是注意,afterExecute和UncaughtExceptionHandler都不适用submit。因为通过上面的FutureTask.run()不难发现,它自己对Throwable进行了try-catch,封装到了outcome属性,所以底层方法execute的Worker是拿不到异常信息的。
17、shutdown和shutdownNow的区别
- shutdown => 平缓关闭,等待所有已添加到线程池中的任务执行完再关闭。
- shutdownNow => 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务。
18、Spring中使用的@Async注解,底层就是基于SimpleAsyncTaskExecutor去执行任务,只不过它不是线程池,而是每次都新开一个线程。
最佳实践总结
-
【强制】使用ThreadPoolExecutor的构造函数声明线程池,避免使用Executors类的 newFixedThreadPool和newCachedThreadPool。
-
【强制】 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。即threadFactory参数要构造好。
-
【建议】建议不同类别的业务用不同的线程池。
-
【建议】CPU密集型任务(N+1):这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核心数)+1,比CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用CPU的空闲时间。
-
【建议】I/O密集型任务(2N):这种任务应用起来,系统会用大部分的时间来处理I/O交互,而线程在处理I/O的时间段内不会占用CPU来处理,这时就可以将CPU交出给其它线程使用。因此在I/O密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是2N。
-
【建议】workQueue不要使用无界队列,尽量使用有界队列。避免大量任务等待,造成OOM。
-
【建议】如果是资源紧张的应用,使用allowsCoreThreadTimeOut可以提高资源利用率。
-
【建议】虽然使用线程池有多种异常处理的方式,但在任务代码中,使用try-catch最通用,也能给不同任务的异常处理做精细化。
-
【建议】对于资源紧张的应用,如果担心线程池资源使用不当,可以利用ThreadPoolExecutor的API实现简单的监控,然后进行分析和优化。
170万+

被折叠的 条评论
为什么被折叠?



