【java并发】线程池
0、线程活跃性与安全性
活跃性:期望的多线程中的事件一定发生。活跃性问题:线程饥饿死锁。
安全性:永远不发生错误的事情。
1、线程饥饿死锁
什么是饥饿死锁?
(1)线程池大小为1,正在执行的任务向线程池提交了一个新任务,而当前的这个任务要等到新任务执行后,返回结果才会结束。这种情况会导致饥饿死锁。
(2)当线程池比较大时,线程池中正在执行的任务,都要等待线程队列中任务的执行结果。也会发生饥饿死锁。
饥饿死锁,实际上就是由于任务之间的相互依赖导致的。
2、设置线程池大小的方法
在代码中通常不会限制线程池的大小。一般可以通过配置机制设置。
原则:不宜过大,不宜过小。过大,会导致大量的线程竞争CPU,导致消耗内存,最终导致耗尽资源;过小,资源得不到充分利用,系统吞吐量较低。
设置方法:
(1)计算密集型:线程池的大小为CPU的个数加1,即:Nthread = Ncpu + 1;
(2)IO密集型:
a.基本计算方法
Nthread = Ncpu * Ucpu * (1 + W/C)
其中,
Ucpu:CPU的使用率,范围0~1
W:CPU使用过程中的等待时间
C:CPU的计算时间
b.外加其他资源的限制
如:内存、文件句柄、套接字句柄、数据库连接。
计算方法,MAX(Nthread) = 资源总大小/每个任务所需资源大小
c.受其他资源池的限制
如:数据库连接池大小的限制。数据库连接池与线程池大小,二者相互制约。
3、配置ThreadPoolExecutor
(1)各个类之间的关系
继承关系:
Executor->ExecutorService->AbstractExecutorService->ThreadPoolExecutor
(2)生成ThreadPoolExecutor
Executors框架提供了newCachedThreadPool、newfFxedThreadPool、newSingleThreadPool这些方法来生成不同内部参数的ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
newfFxedThreadPool:可以设置线程池的基本参数。
newCachedThreadPool:线程池大小无界,最大为Integer,MAX_VAULE。基本大小为1。超时时间为1min。
(3)通过任务队列加一级线程的缓冲
newfFxedThreadPool:适合使用的线程队列:有界队列。如:ArrayBlockingQueue、有界的LinkedBlockingQueue、PriorityBlockingQueue
newCachedThreadPool:适合使用无界队列:无界的LinkedBlockingQueue
(4)饱和策略
饱和策略是为有界队列设计的。
只有当有界队列被填满时,才会触发饱和策略进行任务的处理。
饱和策略有:……
调用者运行策略,既不会抛弃任务,也不会抛出异常。
将饱和后的一个任务,在调用execute的主线程执行。执行期间,不再接受新的任务进入线程池。
这种策略的过载场景会实现一种高负载情况下的平缓的性能降低。因为其过载过程是:线程池->工作队列->应用程序->TCP->客户端。
(5)ThreadPoolExecutor是通过线程工厂方法创建线程的。线程工厂可以自己定制。