线程池的饱和策略与阻塞队列
文章目录
一、线程池的饱和状态
1、线程池的处理过程
提交一个任务到线程池中,线程池的处理流程如下:
- 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
- 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
2、饱和状态的满足条件
- 任务队列满了
- 包括SynchronousQueue队列的特殊情况,由于它是无缓冲的,所以可以看成非常容易满。这个时候如果达到maxSize,就会触发。
- 线程池满了
- 对于linkedBlockingQueue是不会触发的。
二、饱和策略
饱和策略是针对线程池出现饱和情况的处理,通常有四种:
AbortPolicy
- 直接抛出异常拒绝。
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
CallerRunsPolicy
- 调用 调用线程执行,(调用线程即 那个调用pool.exe…的线程)
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
DiscardOldestPolicy
- 丢弃队列最近一个任务,执行当前任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
DiscardPolicy
- 直接丢弃,会造成丢失。
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
以上就是四种策略,当然,我们通常会自定义自己的策略,如下:
//设置饱和策略
((ThreadPoolExecutor) manualPool).setRejectedExecutionHandler((r, executor) -> {
//(1)use executor retry
//but may occurred the stackOverflow
//executor.execute(r);
//(2)
//use current thread run it
// r.run();
});
三、阻塞队列
在线程池的执行任务过程中,会用到任务队列,work线程就会从队列中poll()出任务进行执行。队列可以分为两种,一种是有界,一种是无界,两种有很大区别,下面进入讲解。
1、有界队列
ArrayBlockingQueue
这是一个有界的阻塞队列,可以指定队列的大小,即任务的数量。具体的处理情况如下:
- 核心池未满,创建线程进行执行任务
- 队列未满,任务数超过核心线程数
- 任务加入队列,等待执行。
- 队列满了,任务数超过核心线程数
- 开辟maxSize-coreSize个线程去执行任务(这部分线程具有keepAlive)
- 如果线程数达到了maxSize,这个时候任务队列也满,那么,就会执行饱和策略。
2、无界队列
LinkedBlockingQueue
这是基于链表结构的阻塞队列,因此是无界的,该队列受限于核心线程数,与池外线程无关。使用时务必注意。
- 吞吐量比数组阻塞队列高,延时也比较低,主要受限于核心线程数。
- maxSize-coreSize的池外线程数与它无关,因为它只会调用核心池进行处理。
- 延时较低
SynchronousQueue
这是无缓冲的阻塞队列,无缓冲,意味着每个任务入队必须等待一个任务出队,否则阻塞。吞吐量通常要高于链表队列。但是以下是一些需要注意的地方:
- maxSize需要较大,否则容易触发饱和策略。
- 因为当任务量多,在第一个任务出队之前,其它任务都进不了队,就会创建池外线程进行任务的执行。这个时候如果池外线程允许的数量太小,就会触发饱和策略。
- keepAlive与池外线程仍然相关。
- 当maxSize够大,弹性吞吐量也能够很高,当然创建池外线程的消耗也会增大,但是从某种意义上讲,能够保证的吞吐量是足够大的。
- 延时相对较高。
demo
public class ThreadPoolUsage {
static class DefaultThreadFactory implements ThreadFactory {
/**
* int type can support the thread nums.
*/
private AtomicInteger threadNo = new AtomicInteger(1);
private final String nameStart;
private final String nameEnd = "]";
private final boolean isDaemon;
public DefaultThreadFactory(String poolName, boolean isDaemon) {
this.isDaemon = isDaemon;
this.nameStart = "[" + poolName + "-";
}
@Override
public Thread newThread(Runnable r) {
String threadName = this.nameStart + this.threadNo.getAndIncrement() + nameEnd;
Thread newThread = new Thread(r, threadName);
//set daemon
newThread.setDaemon(isDaemon);
if (newThread.getPriority() != 5) {
newThread.setPriority(5);
}
return newThread;
}
}
public static void main(String[] args) {
//手动创建
//coreSize核心池的线程数,这些线程在创建后不会被回收,除非关闭池
//maxSize=coreSize+restSize;
//restSize的线程会有keepAlive参数,当线程idle keepAliveTime,就会被回收
//timeUnit 即keepAlive的时间单位
ExecutorService manualPool = new ThreadPoolExecutor(8, Integer.MAX_VALUE, 60, TimeUnit.MINUTES,
new LinkedBlockingDeque<>(), new DefaultThreadFactory("QG", false));
//pre Start all core thread.
((ThreadPoolExecutor) manualPool).prestartAllCoreThreads();
//设置饱和策略
((ThreadPoolExecutor) manualPool).setRejectedExecutionHandler((r, executor) -> {
//(1)use executor retry
//but may occurred the stackOverflow
//executor.execute(r);
//(2)
//use current thread run it
r.run();
});
long s=System.currentTimeMillis();
for (int i = 0; i < 3000; i++) {
manualPool.execute(() -> System.err.println(Thread.currentThread().getName()));
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("======="+(System.currentTimeMillis()-1000-s));
//这是由线程池完成的任务数量
System.err.println("numbers of tasks completed by pool workers:"+((ThreadPoolExecutor) manualPool).getCompletedTaskCount());
System.err.println("latest pool max size:"+((ThreadPoolExecutor) manualPool).getLargestPoolSize());
//gracefully shutdown.
// List<Runnable> waitRunTasks = manualPool.shutdownNow();
}
}
- linkedBlockedQueue
- 耗时5MS
- SynchronousQueue
- 耗时77MS
当然,就吞吐量而言,肯定是SynchronousQueue更大,但是延时更高。而且这个SynchronousQueue的maxSize最好设置更大一些,否则容易出现饱和。