线程池你真不来了解一下吗?(二)

简介: 笔记

三、ThreadPoolExecutor详解


这是用得最多的线程池,所以本文会重点讲解它。

我们来看看顶部注释:

12.jpg


3.1内部状态


13.jpg

变量ctl定义为AtomicInteger,记录了“线程池中的任务数量”和“线程池的状态”两个信息

14.jpg

线程的状态:

  • RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理。
  • SHUTDOWN:线程池不可以接受新任务,但是可以对已添加的任务进行处理。
  • STOP:线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
  • TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
  • TERMINATED:线程池彻底终止的状态

15.jpg

各个状态之间转换:

16.jpg


3.2已默认实现的池


下面我就列举三个比较常见的实现池:

  • newFixedThreadPool
  • newCachedThreadPool
  • SingleThreadExecutor

如果读懂了上面对应的策略呀,线程数量这些,应该就不会太难看懂了。


3.2.1newFixedThreadPool


一个固定线程数的线程池,它将返回一个corePoolSize和maximumPoolSize相等的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


3.2.2newCachedThreadPool


非常有弹性的线程池,对于新的任务,如果此时线程池里没有空闲线程,线程池会毫不犹豫的创建一条新的线程去处理这个任务

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }


3.2.3SingleThreadExecutor


使用单个worker线程的Executor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }


3.3构造方法


我们读完上面的默认实现池还有对应的属性,再回到构造方法看看

  • 构造方法可以让我们自定义(扩展)线程池
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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. 指定核心线程数量
  2. 指定最大线程数量
  3. 允许线程空闲时间
  4. 时间对象
  5. 阻塞队列
  6. 线程工厂
  7. 任务拒绝策略

再总结一遍这些参数的要点:

线程数量要点

  • 如果运行线程的数量少于核心线程数量,则创建新的线程处理请求
  • 如果运行线程的数量大于核心线程数量,小于最大线程数量,则当队列满的时候才创建新的线程
  • 如果核心线程数量等于最大线程数量,那么将创建固定大小的连接池
  • 如果设置了最大线程数量为无穷,那么允许线程池适合任意的并发数量

线程空闲时间要点:

  • 当前线程数大于核心线程数,如果空闲时间已经超过了,那该线程会销毁

排队策略要点

  • 同步移交:不会放到队列中,而是等待线程执行它。如果当前线程没有执行,很可能会新开一个线程执行。
  • 无界限策略:如果核心线程都在工作,该线程会放到队列中。所以线程数不会超过核心线程数
  • 有界限策略:可以避免资源耗尽,但是一定程度上减低了吞吐量

当线程关闭或者线程数量满了和队列饱和了,就有拒绝任务的情况了:

拒绝任务策略:

  • 直接抛出异常
  • 使用调用者的线程来处理
  • 直接丢掉这个任务
  • 丢掉最老的任务


四、execute执行方法


execute执行方法分了三步,以注释的方式写在代码上了~

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果线程池中运行的线程数量<corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程池中运行的线程数量>=corePoolSize,且线程池处于RUNNING状态,且把提交的任务成功放入阻塞队列中,就再次检查线程池的状态,
            // 1.如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。
            // 2.否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。
        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);
        }
        // 如果以上两种case不成立,即没能将任务成功放入阻塞队列中,且addWoker新建线程失败,则该任务由当前 RejectedExecutionHandler 处理。
        else if (!addWorker(command, false))
            reject(command);
    }


五、线程池关闭


ThreadPoolExecutor提供了shutdown()shutdownNow()两个方法来关闭线程池

shutdown() :

17.jpg

shutdownNow():

18.jpg

区别:

  • 调用shutdown()后,线程池状态立刻变为SHUTDOWN,而调用shutdownNow(),线程池状态立刻变为STOP
  • shutdown()等待任务执行完才中断线程,而shutdownNow()不等任务执行完就中断了线程。


六、总结


本篇博文主要简单地将多线程的结构体系过了一篇,讲了最常用的ThreadPoolExecutor线程池是怎么使用的~~~


目录
相关文章
|
缓存 Java 应用服务中间件
线程池的10个坑你都遇到过吗
日常开发中,为了更好管理线程资源,减少创建线程和销毁线程的资源损耗,我们会使用线程池来执行一些异步任务。但是线程池使用不当,就可能会引发生产事故。大家看完肯定会有帮助的~
228 0
|
22天前
|
消息中间件 Kubernetes Java
记两个有关线程池的小问题
最近小伙伴们找我查的问题里,有两个与线程池相关的,最终都是花了一些时间才揪出原因所在,做一下记录。
38 1
|
3月前
|
算法 Java
线程池
【8月更文挑战第22天】
42 4
|
3月前
|
Java 调度
基于C++11的线程池
基于C++11的线程池
|
4月前
|
缓存 Java
线程池使用小结
线程池使用小结
26 0
|
5月前
|
存储 缓存 安全
线程池相关详解
线程池相关详解
|
5月前
|
Java
线程池的实现
线程池的实现
33 0
|
6月前
|
存储 Java 调度
浅谈线程池
浅谈线程池
38 1
|
6月前
|
Java C++
c++简单线程池实现
c++简单线程池实现
|
Java
6. 实现简单的线程池
6. 实现简单的线程池
54 0