1. 举个案例,复现异常:
public class ThreadPoolExecutorTest implements Runnable {
int indexV = 0;
public ThreadPoolExecutorTest(int index) {
indexV = index;
}
public static void main(String[] args) {
// 由于ArrayBlockingQueue内部只使用了一个锁来隔离读和写的操作,因此效率没有使用了两个锁来隔离读写操作的LinkedBlockingQueue高,故而不推荐使用ArrayBlockingQueue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(10);// 无界队列,但是可以固定大小;
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, queue);
for (int index = 0; index < 100; index++) {
try {
poolExecutor.submit(new ThreadPoolExecutorTest(index));
} catch (RejectedExecutionException e) {
e.printStackTrace();
System.out.println("线程池关闭后不允许向线程池中添加任务");
}
}
}
@Override
public void run() {
System.out.println("厉害咯,我的哥: " + indexV);
}
}
执行以上代码,会报RejectedExecutionException异常,见文知义是某些线程被线程池执行器拒绝执行;
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@891d76 rejected from java.util.concurrent.ThreadPoolExecutor@121e5a[Running, pool size = 10, active threads = 7, queued tasks = 3, completed tasks = 14]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:110)
at javabasic.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:51)
2.ThreadPoolExecutor 方法的参数定义:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数含义:
序号 | 参数 | 含义 | 备注 |
1 | corePoolSize | 核心线程数量,线程池初始化时设定 | corePoolSize 大小和 maximumPoolSize 大小一致的话 线程池中的线程将不会空闲、 keepAliveTime 和 timeUnit 就不会再起作用 |
2 | maximumPoolSize | 线程池最大线程数(非核心线程) 核心线程 和 非核心线程 共同使用线程池、但是核心线程是不会被回收的、回收条件是线程池中的线程数量大于核心线程数 | |
3 | keepAliveTime | 如果当前线程池中线程数大于corePoolSize。多余的线程、在等待keepAliveTime时间后如果还没有新的线程任务指派给它、它就会被回收 | |
4 | unit | 等待时间keepAliveTime的单位 | |
5 | workQueue | 等待队列, | 默认SynchronousQueue一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程; 可以使用无界队LinkedBlockingQueue; 有界队列ArrayBlockingQueue; 以及优先级队列PriorityBlockingQueue |
6 | RejectedExecutionHandler | 饱和策略(分4中) | AbortPolicy(默认):直接抛弃 CallerRunsPolicy:用调用者的线程执行任务(始终此种方式,可以避免线程被线程池拒绝的情况) DiscardOldestPolicy:抛弃队列中最久的任务 DiscardPolicy:抛弃当前任务 |
3.异常产生的原因:
当线程池里的线程都繁忙的时候,新任务会被提交给阻塞队列保存,当提交给阻塞队列的任务,超出了该队列的最大容量时。线程池就会拒绝接收新任务,随即抛出异常;简单的理解为:队列的存储空间设置小了,但是实际业务中千万不要指着调整队列的大小来完全解决该问题;再往下看...
4. RejectedExecutionHandler的四种饱和策略:
序号 | 策略名称 | 含义 | 备注 |
1 | AbortPolicy | 终止策略是默认的饱和策略,当队列满时,会抛出一个RejectExecutionException异常,客户可以捕获这个异常,根据需求编写自己的处理代码 | 不是解决问题的根本 |
2 | DiscardPolicy | 策略会悄悄抛弃该任务。 | 建议最好不用,水太深 |
3 | DiscardOldestPolicy | 策略将会抛弃下一个将要执行的任务,如果此策略配合优先队列PriorityBlockingQueue,该策略将会抛弃优先级最高的任务 | |
4 | CallerRunsPolicy | 调用者运行策略,该策略不会抛出异常,不会抛弃任务,而是将任务回退给调用者线程执行(调用execute方法的线程),由于任务需要执行一段时间,所以在此期间不能提交任务,从而使工作线程有时间执行正在执行的任务。 | 非常适合我们的业务场景 |
5. 解决办法,给定义的线程池加一个饱和策略:
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, queue, new CallerRunsPolicy());
6.为什么不建议使用FixedThreadPool,SingleThreadPool;CachedThreadPool,ScheduledThreadPool四种线程池:
序号 | 线程池名称 | 规则 | 问题点 |
1 | FixedThreadPool | 允许的请求队列长度为 Integer.MAX_VALUE | 会堆积大量的请求,从而导致 OOM |
2 | SingleThreadPool | ||
3 | CachedThreadPool | 允许的创建线程数量为 Integer.MAX_VALUE | 会创建大量的线程,从而导致 OOM |
4 | ScheduledThreadPool |