线程池

为什么要使用线程池

线程池是并发场景中比较常见的运用,几乎所有的异步或并发执行任务的程序都可以使用线程池。在开发中使用线程池能带来以下好处。

  1. 降低资源消耗。重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不用等待线程的创建,直接执行。
  3. 提高线程的可管理性。线程是稀缺资源,不会无限制地创建。不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

    线程池的工作原理

    线程池的创建依赖于 ThreadPoolExecutor,它的构造函数如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public ThreadPoolExecutor(int corePoolSize, //核心线程数量
    int maximumPoolSize, //最大线程数
    long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
    TimeUnit unit, //存活时间单位
    BlockingQueue<Runnable> workQueue, //保存执行任务的队列
    ThreadFactory threadFactory,//创建新线程使用的工厂
    RejectedExecutionHandler handler //当任务无法执行的时候的处理方式
    ) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    threadFactory, defaultHandler);
    }

创建线程池所需要的参数:

  1. corePoolSize (核心线程数量): 当提交一个任务到线程池中,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行任务也会创建新的线程,直到池中的线程数达到 corePoolSize 的大小就不再创建。
  2. workQueue (工作/任务队列):用于保存等待执行的任务的阻塞队列。
  3. maximumPoolSize (最大线程数): 线程池所允许创建的最大线程数量,如果工作队列满了,并且已创建的的线程数小于最大线程数,此时线程池会临时创建新的线程执行任务。
  4. keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间,超过时间会被回收。
  5. unit: 线程保持活动时间的单位。
  6. threadFactory: 用于设置创建线程的工厂。
  7. handler (饱和处理器):当工作队列为有界队列,并且池中的线程数量已经达到了最大线程数,此时新提交的任务就会由 handler 进行饱和处理,抛出异常。

当提交一个新任务到线程池中,线程池的处理流程如下:

  1. 判断线程是否已经达到核心线程数,如果当前池中线程数少于核心线程数,创建一个新线程,否则进入下一个流程。
  2. 判断工作队列是否已满,如果未满,则将任务放入队列中,如果队列已满,则进行下一个流程。
  3. 判断当前池中线程数是否已达到最大线程数,如果未达到,则创建新的线程并执行任务,如果已达到最大线程数,则任务会被拒绝。
  4. 当其他的线程执行完任务时,会进入空闲状态,如果队列中有任务,会取出来执行,当队列为空之后,超过空闲存活时间的队列会被回收。
    流程图:
    Alt
    Alt

execute方法流程分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void execute(Runnable command) {  
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {//1.当前池中线程比核心数少,新建一个线程执行任务
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//2.核心池已满,但任务队列未满,添加到队列中
int recheck = ctl.get();
//任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
if (! isRunning(recheck) && remove(command))
reject(command); //如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);//如果之前的线程已被销毁完,新建一个线程
}
else if (!addWorker(command, false)) //3.核心池已满,队列已满,试着创建一个新线程
reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

submit和execute的区别

向一个线程池提交任务,可以使用 submitexecute,这两者有什么区别呢?

  • execute 只能接受 Runnable 类型的任务,execute 没有返回值

  • submit 不管是 Runnable 还是 Callable 类型的任务都可以接受,但是 Runnable返回值均为void,所以使用 Futureget() 获得的还是 null

0%