目录
没见原理,讲的是用法:对线程池进行配置和调优
一 在任务与执行策略之间的隐形解耦
不同的线程池的执行策略肯定不一样
并非
所有的任务都能适用
所有的执行策略,有些类型的任务需要明确地指定执行策略:
- 依赖性任务。可能会有死锁问题
- 使用线程封闭机制的任务。并发问题
- 对响应时间敏感的任务。需要快!
- 使用
ThreadLocal
的任务.线程可能会被回收,会异常会销毁
线程饥饿死锁
现象
线程池只有一个线程(或者资源紧张),a任务中又提交了一个b任务到线程池。并且a要等待b完成。而b在等a腾出线程资源
解决办法
加线程
运行时间较长的任务
现象
大量的耗时任务会拖慢一些不耗时任务的运行时间(要等嘛
)
解决办法
限定任务等待资源的时间(许多类库的可阻塞方法都同时定义了限时版本
),比如:Thread.join\BlockingQueue.put等
这是针对任务层面的方法,
即需要任务编写者维护。
二 设置线程池的大小
理想大小取决于被提交任务的类型以及所部署系统的特性(硬件)
考虑cpu利用率,等待时间、是否依赖其他资源(内存、数据库连接)等等
三 配置ThreadPoolExecutor
ThreadPoolExecutor为一些Executor提供了基本的实现,这些Executor是由Executors中的newCachedThreadPool等工厂方法
返回的
之前在简书也写过相关文章,参考:线程池
如果上面的默认实现不能满足,可以通过ThreadPoolExecutor的构造函数来实例化一个对象
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
线程的创建与销毁
corePoolSize、maximumPoolSize、keepAliveTime三者共同负责线程的创建与销毁。
- corePoolSize:基本(目标)大小。没有任务执行时(
在创建ThreadPoolExecutor初期,线程不会立即启动,而是等到有任务提交时才会启动,除非调用prestartCoreThread
)的线程池大小。 - maximumPoolSize:如果工作队列满了,线程池还是会继续创建线程,直至maximumPoolSize的线程数。
- keepAliveTime:存活时间。如果空闲时间超过了keepAliveTime,会被
标记
成可回收的,并且线程池数量大于corePoolSize时。线程会被回收
管理任务队列
有限的线程池会限制可并发执行的任务数量,但如果无限制的创建线程,会导致不稳定性。
解决办法
固定大小的线程池,并配一个存放任务的工作队列
ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务,基本排队方法有3种
- 无界队列(newFixedThreadPool和newSingleThreadPool采用的方式)
- 有界队列(更加
稳妥
),配合饱和策略一起用 - 同步移交(适用与
非常大或者无界的线程池
),也可能用到饱和策略。参考:SynchronousQueue解析
饱和策略
当有界队列被填满后,饱和策略开始发挥作用,可以通过setRejectedExecutionHandler
修改。jdk提供了几种不同的策略实现:
- 默认是中止策略:抛出不受检的异常
- 调用者运行,比较有意思:谁提交的,谁自己来执行(这样调用者就去执行任务,还会阻塞。减缓了提交速度)
如何阻塞提交速度呢?
使用Semaphore
(书上有例子,但应该很少用到)
线程工厂
每当线程池需要创建新的线程时,都是通过线程工厂方法来完成的。默认创建的线程都是新的、非守护的线程
.我们可以指定一个线程工厂方法,定制线程池的配置信息。
我们可以自定义线程工厂,需要注意:如果在应用程序中徐娅利用安全策略来控制对某些特殊代码库的访问权限,那么可以通过PrivilegedThreadFactory
工厂来定制自定义的线程工厂(目前没接触过这个,不太了解应用场景)
在调用构造函数后再定制ThreadPoolExecutor
基本属性仍可以通过setXxx方法配置。
如果不想创建后被修改,需要用到Executors的unconfigurableExecutorService来处理一下(newSingleThreadExecutor就是这样做的
)
四 扩展ThreadPoolExecutor
五 递归算法的并行化
知道个概念就好了,没细看