Java线程池知识总结
为什么会有线程池
-
创建线程开销大,主要是时间和内存开销。
Java线程是映射到操作系统用户线程,创建时需要分配栈空间、计数器等。占用空间大小由-Xss控制,默认是1M。
-
每次手动创建线程难管理。
机器的CPU和内存资源能支持的线程数是有限的,创建过多的线程会争抢CPU和占用过多内存,造成程序响应慢甚至“假死”。
线程池原理
-
数据库连接池等池化资源,一般使用流程:获取资源 -> 执行操作 -> 归还资源。
-
线程池则不同,实现方式是:生产者-消费者模式。
线程池使用方是生产者,生产任务;线程池是消费者,每个Worker线程循环获取任务执行。
循环获取任务源码参考java.util.concurrent.ThreadPoolExecutor.runWorker(Worker)
核心线程数corePoolSize、最大线程数maximumPoolSize、缓冲队列workQueue、拒绝策略RejectedExecutionHandler怎么起效的?
-
先根据核心线程数corePoolSize创建线程,当核心线程都忙还有新任务来时,任务就进入缓冲队列workQueue;
-
当缓冲队列满时,再根据最大线程数maximumPoolSize创建新线程(触发创建新线程的那个任务就是这个线程的第一个任务);
-
当达到最大线程数还有任务来时,则执行拒绝策略RejectedExecutionHandler。
为什么是上述策略?
线程池的设计目标是重用线程。如果一出现任务堆积就新建线程,可能会导致频繁创建销毁线程,与线程池设计目标相违背。
线程池使用注意事项
-
给线程指定有意义的名字。
仿照java.util.concurrent.Executors.DefaultThreadFactory实现自定义ThreadFactory,或者在spring框架下使用org.springframework.scheduling.concurrent.CustomizableThreadFactory。
-
使用有界队列做缓冲区。
使用ArrayBlockingQueue或者带capacity的LinkedBlockingDeque,避免OOM;避免使用Executors工厂方法创建线程池。
-
代码捕获所有异常。
线程池执行任务时遇到RuntimeException不会有任何信息,遇到问题不方便排查。
run(){
try{
//do job
}catch(Throwable e){
//自定义处理
}
}
线程池不是银弹
我们不能仅仅因为程序慢,就使用线程池异步执行,重要的还是程序的执行效率。
更多参考
美团这篇文章图文并茂,深入讲解线程池实现原理及动态线程池方案。Java线程池实现原理及其在美团业务中的实践
一种动态线程池方案实现。yinjihuan的动态线程池方案实现