最近在看线程池相关的内容,之前总觉得这块很复杂,难以理解,近来跟打通了任督二脉一样,茅塞顿开。话不多说,直接附上最近的一些些收货。
Executor这个线程池框架中,有几个很典型的线程池。
-
1)newCachedThreadPool 是一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
-
2)newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
-
3)newFixedThreadPool 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
-
4)newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
下面附上图片一张用来介绍核心概念以及他们之间的差异
这几个核心参数,重中之重是需要把握corePoolSize与maximumPoolSize与当前正在运行的线程总数以及等待队列的容量的关系。假设当前corePoolSize的大小是1,maximumPoolSize=2,当前等待队列实现是blockQueue的大小是3。
下面跟着做一个小小的推演:
1.此时客户端发过来了1-4标号的4个任务。这四个任务先后的执行顺序
此时运行的过程是标号1的进入核心线程中,然后标号2,3,4的线程是进入等待队列中,当1执行完毕的时候,从等待队列中取出标号2的任务执行,依次类推。
2.如果客户端发过来了1-5标号的5个任务,执行顺序又当如何呢?
此时的执行顺序是1和5号同时执行,然后依次执行2,3,4任务。
why?此时是因为目前实际占用核心线程的只有1号,2,3,4在等待队列中,5号任务来的时候发现队列满了,但是当前的总线程数只有1号一个,它不大于maximunPoolSize,所以就直接创建新的线程执行任务。
3.如果客户端发过来了1-6标号的6个任务,此时会出现什么状况?
此时,1.5号任务还是会几乎同时执行,然后抛出一个RejectedExecutionException的错误,然后2,3,4好任务依次执行。1号5号2个任务已经达到了线程池的最大值,2,3,4还是在等待队列中,此时6号任务来了之后,发现线程池中满了,等待队列中也满了,已经没有能力再来执行6号任务。于是线程池会直接抛出一个拒绝6号任务的Exception。
下面附上代码:
public class Task implements Runnable{
private Integer taskId;
private String taskName;
public Integer getTaskId() {
return taskId;
}
public void setTaskId(Integer taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Task(Integer taskId, String taskName) {
this.taskId = taskId;
this.taskName = taskName;
}
@Override
public void run() {
try {
System.out.println("run taskId =" + this.taskId);
Thread.sleep(5000L);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "Task{" +
"taskId=" + taskId +
", taskName='" + taskName + '\'' +
'}';
}
然后是执行的main方法
public class MyThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
2,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue(3));//,
// new Rejected());
Task mt1 = new Task(1,"任务1");
Task mt2 = new Task(2, "任务2");
Task mt3 = new Task(3, "任务3");
Task mt4 = new Task(4, "任务4");
//任务五
Task mt5 = new Task(5, "任务5");
Task mt6 = new Task(6, "任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
}
附加:当然当线程无能力执行多余的任务的时候,程序直接报错,肯定是不大友好的,可以自己写一个类继承RejectedExecutionHandler,然后根据需要写上友好处理的代码。
public class Rejected implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(Thread.currentThread().getName());
System.out.println("打回请求,记录日志");
}
}