一、线程池
1. 线程池执行流程
线程池是为了避免线程频繁的创建和销毁带来的性能消耗,而建立的一种池化技术。 它是把已创建的线程放入"池"中,当有任务来临时就可以重用已有的线程,无需等待创建的过程。 这样就可以有效提高程序的响应速度。 但如果要说线程池的话一定离不开 ThreadPoolExecutor
线程池执行器。
2. 阿里巴巴《Java 开发手册》
线程池不允许使用 Executors
去创建,而是通过 ThreadPoolExecutor
的方式。 这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。
3. Executors
创建线程池对象的弊端
FixedThreadPool
和 SingleThreadPool
。
允许的请求队列长度为 Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致 OOM
。
CachedThreadPool
和 ScheduledThreadPool
。
允许的创建线程数量为 Integer.MAX_VALUE
,可能会创建大量的线程,从而导致 OOM
。
看 Executors
的源码会发现。
Executors.newFixedThreadPool()
、Executors.newSingleThreadExecutor()
和 Executors.newCachedThreadPool()
等方法的底层都是通过 ThreadPoolExecutor
实现的。
二、ThreadPoolExecutor
线程池执行器
1. 构造方法
ThreadPoolExecutor
的核心参数,指的是它在构建时需要传递的参数。
public ThreadPoolExecutor ( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue < Runnable > workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if ( corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0 )
throw new IllegalArgumentException ( ) ;
if ( workQueue == null || threadFactory == null || handler == null )
throw new NullPointerException ( ) ;
this . acc = System . getSecurityManager ( ) == null ?
null :
AccessController . getContext ( ) ;
this . corePoolSize = corePoolSize;
this . maximumPoolSize = maximumPoolSize;
this . workQueue = workQueue;
this . keepAliveTime = unit. toNanos ( keepAliveTime) ;
this . threadFactory = threadFactory;
this . handler = handler;
}
int corePoolSize
:核心线程数。
表示线程池的常驻核心线程数。 如果设置为 0
,则表示在没有任何任务时,销毁线程池。 如果大于 0
,即使没有任务时,也会保证线程池的线程数量等于此值。 但需要注意,此值如果设置的比较小,则会频繁的创建和销毁线程。 如果设置的比较大,则会浪费系统资源,所以开发者需要根据自己的实际业务来调整此值。
int maximumPoolSize
:最大线程数。
表示线程池在任务最多时,最大可以创建的线程数。 官方规定此值必须大于 0
,也必须大于等于 corePoolSize
。 此值只有在任务比较多,且不能存放在任务队列时,才会用到。
long keepAliveTime
:空闲线程存活时间。
表示线程的存活时间,当线程池空闲时并且超过了此时间,多余的线程就会销毁。 直到线程池中的线程数量销毁到等于 corePoolSize
为止。 如果 maximumPoolSize
等于 corePoolSize
,那么线程池在空闲的时候也不会销毁任何线程。
TimeUnit unit
:存活时间单位。
表示存活时间的单位,它是配合 keepAliveTime
参数共同使用的。
BlockingQueue<Runnable> workQueue
:任务队列。
表示线程池执行的任务队列。 当线程池的所有线程都在处理任务时,如果来了新任务就会缓存到此任务队列中排队等待执行。
ThreadFactory threadFactory
:线程工厂。
表示线程的创建工厂,此参数一般用的比较少。 我们通常在创建线程池时不指定此参数,它会使用默认的线程创建工厂的方法来创建线程。
RejectedExecutionHandler handler
:拒绝策略
表示指定线程池的拒绝策略。 当线程池的任务已经在缓存队列 workQueue
中存储满了之后,并且不能创建新的线程来执行此任务时,就会用到此拒绝策略,它属于一种限流保护的机制。
2. BlockingQueue
任务队列
2.1 SynchronousQueue
直接提交队列
SynchronousQueue
是一个特殊的 BlockingQueue
。
它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒。 反之每一个删除操作也都要等待对应的插入操作。
@Test
public void test ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 2
, 1000
, TimeUnit . MILLISECONDS
, new SynchronousQueue < Runnable > ( )
, Executors . defaultThreadFactory ( )
, new ThreadPoolExecutor. AbortPolicy ( ) ) ;
for ( int i = 0 ; i < 3 ; i++ ) {
threadPoolExecutor. execute ( new ThreadTask ( "任务-" + i) ) ;
}
}
2.2 ArrayBlockingQueue
有界任务队列【推荐】
使用 ArrayBlockingQueue
有界任务队列。
若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到 corePoolSize
时,则会将新的任务加入到等待队列中。 若等待队列已满,即超过 ArrayBlockingQueue
初始化的容量,则继续创建线程,直到线程数量达到 maximumPoolSize
设置的最大线程数量。 若大于 maximumPoolSize
,则执行拒绝策略。 在这种情况下,线程数量的上限与有界任务队列的状态有直接关系。
如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在 corePoolSize
以下。 反之当任务队列已满时,则会以 maximumPoolSize
为最大线程数上限。
public void test2 ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 2
, 1000
, TimeUnit . MILLISECONDS
, new ArrayBlockingQueue < Runnable > ( 8 )
, Executors . defaultThreadFactory ( )
, new ThreadPoolExecutor. AbortPolicy ( ) ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
threadPoolExecutor. execute ( new ThreadTask ( "任务-" + i) ) ;
}
}
Task : 任务- 0 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 9 , Thread : pool- 1 - thread- 2 , priority: 0
Task : 任务- 2 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 1 , Thread : pool- 1 - thread- 2 , priority: 0
Task : 任务- 4 , Thread : pool- 1 - thread- 2 , priority: 0
Task : 任务- 3 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 5 , Thread : pool- 1 - thread- 2 , priority: 0
Task : 任务- 6 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 7 , Thread : pool- 1 - thread- 2 , priority: 0
Task : 任务- 8 , Thread : pool- 1 - thread- 1 , priority: 0
2.3 LinkedBlockingQueue
无界的任务队列
线程池的任务队列,可以无限制的添加新的任务。 而线程池创建的最大线程数量,就是你 corePoolSize
设置的数量。
也就是说在这种情况下 maximumPoolSize
这个参数是无效的。 哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到 corePoolSize
后,就不会再增加了。 若后续有新的任务加入,则直接进入队列等待。
当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制。 不然会出现队列中的任务由于无法及时处理,导致一直增长,直到最后资源耗尽的问题。
public void test3 ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 2
, 1000
, TimeUnit . MILLISECONDS
, new LinkedBlockingQueue < Runnable > ( )
, Executors . defaultThreadFactory ( )
, new ThreadPoolExecutor. AbortPolicy ( ) ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
threadPoolExecutor. execute ( new ThreadTask ( "任务-" + i) ) ;
}
}
Task : 任务- 0 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 1 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 2 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 3 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 4 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 5 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 6 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 7 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 8 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 9 , Thread : pool- 1 - thread- 1 , priority: 0
2.4 PriorityBlockingQueue
优先任务队列
public void test4 ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 2
, 1000
, TimeUnit . MILLISECONDS
, new PriorityBlockingQueue < Runnable > ( )
, Executors . defaultThreadFactory ( )
, new ThreadPoolExecutor. AbortPolicy ( ) ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
threadPoolExecutor. execute ( new ThreadTask ( "任务-" + i, i) ) ;
}
}
可以看到除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列。 按优先级进行了重新排列执行,且线程池的线程数一直为 corePoolSize
,也就是只有一个。 通过运行的代码可以看出 PriorityBlockingQueue
它其实是一个特殊的无界队列。
它其中无论添加了多少个任务,线程池创建的线程数也不会超过 corePoolSize
的数量。
只不过其他队列一般是按照先进先出的规则处理任务,而 PriorityBlockingQueue
队列可以自定义规则根据任务的优先级顺序先后执行。
Task : 任务- 0 , Thread : pool- 1 - thread- 1 , priority: 0
Task : 任务- 9 , Thread : pool- 1 - thread- 1 , priority: 9
Task : 任务- 8 , Thread : pool- 1 - thread- 1 , priority: 8
Task : 任务- 7 , Thread : pool- 1 - thread- 1 , priority: 7
Task : 任务- 6 , Thread : pool- 1 - thread- 1 , priority: 6
Task : 任务- 5 , Thread : pool- 1 - thread- 1 , priority: 5
Task : 任务- 4 , Thread : pool- 1 - thread- 1 , priority: 4
Task : 任务- 3 , Thread : pool- 1 - thread- 1 , priority: 3
Task : 任务- 2 , Thread : pool- 1 - thread- 1 , priority: 2
Task : 任务- 1 , Thread : pool- 1 - thread- 1 , priority: 1
3. ThreadFactory
线程工厂
我们也可以自定义一个线程工厂,通过实现 ThreadFactory
接口来完成。 这样就可以自定义线程的名称或线程执行的优先级了。
public ThreadPoolExecutor ( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue < Runnable > workQueue) {
this ( corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
Executors . defaultThreadFactory ( ) ,
defaultHandler) ;
}
public static ThreadFactory defaultThreadFactory ( ) {
return new DefaultThreadFactory ( ) ;
}
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger ( 1 ) ;
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger ( 1 ) ;
private final String namePrefix;
DefaultThreadFactory ( ) {
SecurityManager s = System . getSecurityManager ( ) ;
group = ( s != null ) ? s. getThreadGroup ( )
: Thread . currentThread ( ) . getThreadGroup ( ) ;
namePrefix = "pool-"
+ poolNumber. getAndIncrement ( )
+ "-thread-" ;
}
public Thread newThread ( Runnable r) {
Thread t = new Thread ( group,
r,
namePrefix + threadNumber. getAndIncrement ( ) ,
0 ) ;
if ( t. isDaemon ( ) )
t. setDaemon ( false ) ;
if ( t. getPriority ( ) != Thread . NORM_PRIORITY)
t. setPriority ( Thread . NORM_PRIORITY) ;
return t;
}
}
4. RejectedExecutionHandler
拒绝策略
再有任务添加时,会先判断当前线程池中的线程数,是否大于等于线程池的最大值。
如果是,则会触发线程池的 拒绝策略 。
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建 有界任务队列 。
但有种模式下,如果出现任务队列已满,且线程池创建的线程数达到你设置的最大线程数时。 这时就需要你指定 ThreadPoolExecutor
的 RejectedExecutionHandler 参数。 即合理的拒绝策略 ,来处理 线程池超载 的情况。
4.1 AbortPolicy
终止策略
例如:演示一个 AbortPolicy
的拒绝策略,代码如下:
问题:第 6 个任务来的时候,线程池则执行了 AbortPolicy
拒绝策略,抛出了异常。 原因:因为队列最多存储 2 个任务,最大可以创建 3 个线程来执行任务(2+3=5) 所以当第 6 个任务来的时候,此线程池就忙不过来了。
public void test ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 3
, 10
, TimeUnit . SECONDS
, new LinkedBlockingQueue < > ( 2 )
, new ThreadPoolExecutor. AbortPolicy ( ) ) ;
for ( int i = 0 ; i < 6 ; i++ ) {
threadPoolExecutor. execute ( ( ) -> {
System . out. println ( Thread . currentThread ( ) . getName ( ) ) ;
} ) ;
}
}
4.2 CallerRunsPolicy
把任务交给当前线程来执行
如果线程池的线程数量达到上限,该策略会把任务队列中的任务,放在调用者线程当中运行 。
4.3 DiscardPolicy
忽略此任务(最新的任务)
该策略会默默丢弃无法处理的任务,不予任何处理。 当然使用此策略,业务场景中需允许任务的丢失。
4.4 DiscardOldestPolicy
忽略最早的任务(最先加入队列的任务)
该策略会丢弃任务队列中最老的一个任务。 也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交。
5. execute()
执行
public void execute ( Runnable command) {
if ( command == null )
throw new NullPointerException ( ) ;
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) ;
else if ( workerCountOf ( recheck) == 0 )
addWorker ( null , false ) ;
}
else if ( ! addWorker ( command, false ) )
reject ( command) ;
}
addWorker(Runnable firstTask, boolean core)
方法的参数说明如下:
firstTask :线程应首先运行的任务,如果没有则可以设置为 null
。core :判断是否可以创建线程的阀值(最大值)
如果等于 true
则表示使用 corePoolSize
作为阀值。 false
则表示使用 maximumPoolSize
作为阀值。
二、知识扩展
1. execute()
和 submit()
区别
execute()
和 submit()
都是用来执行线程池任务的,它们最主要的区别是:
submit()
方法可以接收线程池执行的返回值。而 execute()
方法不能接收返回值。
private static ThreadPoolExecutor threadPoolExecutor;
@Test
public void test ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 2
, 10
, 10L
, TimeUnit . SECONDS
, new LinkedBlockingQueue < > ( 20 ) ) ;
threadPoolExecutor. execute ( new Runnable ( ) {
@Override
public void run ( ) {
System . out. println ( "run execute()" ) ;
}
} ) ;
Future < String > submit = threadPoolExecutor. submit ( new Callable < String > ( ) {
@Override
public String call ( ) throws Exception {
System . out. println ( "run submit()" ) ;
return "SUCCESS" ;
}
} ) ;
try {
System . out. println ( submit. get ( ) ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
} catch ( ExecutionException e) {
e. printStackTrace ( ) ;
}
}
从结果可以看出 submit()
方法可以配合 Futrue
来接收线程执行的返回值。
它们的另一个区别是 execute()
方法属于 Executor
接口的方法。 而 submit()
方法则是属于 ExecutorService
接口的方法。
run execute ( )
run submit ( )
SUCCESS
2. 自定义线程工厂
线程池中的线程就是通过 ThreadPoolExecutor
中的 ThreadFactory
,线程工厂创建的。 那么通过自定义 ThreadFactory
,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等。
public void test ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 2
, 1000
, TimeUnit . MILLISECONDS
, new ArrayBlockingQueue < Runnable > ( 5 )
, new ThreadFactory ( ) {
@Override
public Thread newThread ( Runnable r) {
System . out. println ( "线程" + r. hashCode ( ) + "创建" ) ;
Thread thread = new Thread ( r, "threadPoolExecutor-" + r. hashCode ( ) ) ;
return thread;
}
}
, new ThreadPoolExecutor. CallerRunsPolicy ( ) ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
threadPoolExecutor. execute ( new ThreadTask ( "任务-" + i) ) ;
}
}
线程1451043227 创建
线程783286238 创建
Task : 任务- 0 , Thread : threadPoolExecutor- 1451043227 , priority: 0
Task : 任务- 7 , Thread : main, priority: 0
Task : 任务- 6 , Thread : threadPoolExecutor- 783286238 , priority: 0
Task : 任务- 9 , Thread : main, priority: 0
Task : 任务- 2 , Thread : threadPoolExecutor- 783286238 , priority: 0
Task : 任务- 1 , Thread : threadPoolExecutor- 1451043227 , priority: 0
Task : 任务- 3 , Thread : threadPoolExecutor- 783286238 , priority: 0
Task : 任务- 4 , Thread : threadPoolExecutor- 1451043227 , priority: 0
Task : 任务- 5 , Thread : threadPoolExecutor- 783286238 , priority: 0
Task : 任务- 8 , Thread : threadPoolExecutor- 1451043227 , priority: 0
3. 自定义拒绝策略
自定义拒绝策略只需要新建一个 RejectedExecutionHandler
对象。 然后重写它的 rejectedExecution()
方法即可。 如下代码所示:
public void test2 ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 1
, 3
, 10
, TimeUnit . SECONDS
, new LinkedBlockingQueue < > ( 2 )
, new RejectedExecutionHandler ( ) {
@Override
public void rejectedExecution ( Runnable r, ThreadPoolExecutor executor) {
System . out. println ( "执行自定义拒绝策略" ) ;
}
} ) ;
for ( int i = 0 ; i < 6 ; i++ ) {
threadPoolExecutor. execute ( ( ) -> {
System . out. println ( Thread . currentThread ( ) . getName ( ) ) ;
} ) ;
}
}
可以看出线程池执行了自定义的拒绝策略。 我们可以在 rejectedExecution 中添加自己业务处理的代码。 以上代码执行的结果如下:
执行自定义拒绝策略
pool- 1 - thread- 1
pool- 1 - thread- 1
pool- 1 - thread- 1
pool- 1 - thread- 2
pool- 1 - thread- 3
4. ThreadPoolExecutor
扩展
主要是通过重写它的 beforeExecute()
和 afterExecute()
方法实现的。 我们可以在扩展方法中添加日志或者实现数据统计,比如统计线程的执行时间。
private final ThreadLocal < Long > localTime = new ThreadLocal < > ( ) ;
public void test ( ) {
threadPoolExecutor = new ThreadPoolExecutor ( 2
, 4
, 10
, TimeUnit . SECONDS
, new LinkedBlockingQueue < > ( ) ) {
@Override
protected void beforeExecute ( Thread t, Runnable r) {
super . beforeExecute ( t, r) ;
Long sTime = System . nanoTime ( ) ;
localTime. set ( sTime) ;
System . out. println ( String . format ( "%s | beforeExecute | time: %s" ,
t. getName ( ) , sTime) ) ;
}
@Override
protected void afterExecute ( Runnable r, Throwable t) {
super . afterExecute ( r, t) ;
Long eTime = System . nanoTime ( ) ;
Long totalTime = eTime - localTime. get ( ) ;
System . out. println ( String . format ( "%s | afterExecute | time: %s | 耗时: %s" ,
Thread . currentThread ( ) . getName ( ) , eTime, ( totalTime / 1000000.0 ) ) ) ;
}
@Override
protected void terminated ( ) {
super . terminated ( ) ;
System . out. println ( "线程池退出" ) ;
}
} ;
for ( int i = 0 ; i < 3 ; i++ ) {
threadPoolExecutor. execute ( ( ) -> {
System . out. println ( Thread . currentThread ( ) . getName ( ) ) ;
} ) ;
}
threadPoolExecutor. shutdown ( ) ;
}
pool- 1 - thread- 1 | beforeExecute | time: 4013652833313
pool- 1 - thread- 2 | beforeExecute | time: 4013666761362
pool- 1 - thread- 2
pool- 1 - thread- 1
pool- 1 - thread- 2 | afterExecute | time: 4013705529345 | 耗时: 38.767983
pool- 1 - thread- 1 | afterExecute | time: 4013705585177 | 耗时: 52.751864
pool- 1 - thread- 2 | beforeExecute | time: 4013712116298
pool- 1 - thread- 2
pool- 1 - thread- 2 | afterExecute | time: 4013712470585 | 耗时: 0.354287
5. 线程池线程数量
线程池线程数量的设置,没有一个明确的指标。 根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可。
Nthreads = Ncpu * Ucpu * ( 1 + W / C )
三、小结
线程池的使用必须要通过 ThreadPoolExecutor
的方式来创建。
这样可以更加明确线程池的运行规则,规避资源耗尽的风险。 同时,也介绍了 ThreadPoolExecutor
的七大核心参数,包括 核心线程数 和 最大线程数 之间的区别。 当线程池的任务队列没有可用 空闲线程 ,且线程池的线程数量已经达到了 最大线程数 时,则会执行 拒绝策略 。 Java
自动的拒绝策略有 4 种,用户也可以通过重写 rejectedExecution()
来自定义拒绝策略。我们还可以通过重写 beforeExecute()
和 afterExecute()
来实现 ThreadPoolExecutor
的扩展功能。
1. ThreadPoolExecutor 的执行方法有几种?它们有什么区别?
2. 什么是线程的拒绝策略?
3. 拒绝策略的分类有哪些?
4. 如何自定义拒绝策略?
5. ThreadPoolExecutor 能不能实现扩展?如何实现扩展?