线程池是很常用的并发框架,几乎所有需要异步和并发处理任务的程序都可用到线程池。

使用线程池的好处如下

  1. 降低资源消耗:可重复利用已创建的线程池,降低创建和销毁带来的消耗;
  2. 提高响应速度:任务到达时,可立即执行,无需等待线程创建;
  3. 提高线程的可管理性:线程池可对线程统一分配、调优和监控。

原理

线程池的原理非常简单,这里用处理流程来概括:

  1. 线程池判断核心池里的线程是否都在执行任务,如果不是,创建一个新的线程来执行任务;
  2. 如果核心线程池已满,则将新任务存在工作队列中;
  3. 如果工作队列满了,线程数量没有达到线程池上限的前提下,新建一个线程来执行任务;
  4. 线程数量达到上限,则触发饱和策略来处理这个任务;

使用工作队列,是为了尽可能降低线程创建的开销。工作队列用阻塞队列来实现。

阻塞队列

阻塞队列(BlockingQueue)是指支持阻塞的插入和移除元素的队列。

  • 阻塞的插入:当队列满时,阻塞插入元素的线程,直到队列不满;
  • 阻塞的移除:当队列为空,阻塞移除元素的线层,直到队列不为空;

原理:使用通知者模式实现。当生产者往满的队列中添加元素时,会阻塞生产者。消费者移除元素时,会通知生产者当前队列可用。

阻塞队列有以下三种类型,分别是:

  • 有界阻塞队列:ArrayBlockingQueue(数组),LinkedBlockingQueue(链表)
  • 无界阻塞队列:LinkedTransferQueue(链表),PriorityBlockingQueue(支持优先级排序),DelayQueue(支持延时获取元素的无界阻塞队列)
  • 同步移交队列:SynchronousQueue

有界阻塞队列

主要包括ArrayBlockingQueue(数组),LinkedBlockingQueue(链表)两种。有界队列大小与线程数量大小相互配合,队列容量大线程数量小时,可减少上下文切换降低cpu使用率,但是会降低吞吐量。

无界阻塞队列

比较常用的是LinkedTransferQueue。FixedThreadPool就是用这个实现的。无界阻塞队列要慎重使用,因为在某些情况,可能会导致大量的任务堆积到队列中,导致内存飙升。

同步移交队列

SynchronousQueue。不存储元素的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素。用于实现CachedThreadPool线程池。

各个线程池所使用的任务队列映射关系如下:

线程池 阻塞队列
FixedThreadPool LinkedBlockingQueue
SingleThreadExecutor LinkedBlockingQueue
CachedThreadExecutor SynchronousQueue
ScheduledThreadPoolExecutor LinkedBlockingQueue

实现类分析

ThreadPoolExecutor是Java线程池的实现类,是Executor接口派生出来的最核心的类。依赖关系图如下:

这里不得不提到Executor框架,该框架包含三大部分,如下:

  • 任务。被执行任务需要实现的接口:Runnable和Callable;
  • 任务执行。即上述核心接口Executor以及继承而来的ExecutorService。ExecutorService派生出如下两个类:
    • ThreadPoolExecutor:线程池核心实现类;
    • ScheduledThreadPoolExecutor:用来做定时任务;
  • 异步计算的结果。接口Future和实现Future接口的FutureTask类。

线程池创建

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler)

构造方法如下:

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;
}

参数说明:

  • corePoolSize:核心池的线程数量;
  • workQueue:用于保存任务的工作队列;
  • maximumPoolSize:最大线程池的大小;
  • keepAliveTime:当线程数量大于核心池线程数量时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间,多余的线程会被终止;
  • TimeUnit:keepAliveTime的单位;
  • ThreadFactory:线程工厂,可以给线程设置名字;
  • handler:饱和策略。当队列和线程池都满了,会触发饱和策略,来处理新提交的任务。饱和策略以下几种:
    • AbortPolicy:直接抛出异常;
    • CallerRunsPolicy:只用调用者所在线程来运行任务;
    • DiscardOldestPolicy:丢弃最近一个任务并执行当前任务;
    • DiscardPolicy:不处理,丢弃掉。

使用Executors创建线程池

使用工具类Executors可创建三种类型的线程池:FixedThreadPool、SingleThreadExecutor、CachedThreadPool。本质上也是调用上述构造方法。理解了前文的参数解释,下面三种线程池也就容易理解了。

  • FixedThreadPool

可重用固定线程数的线程池。

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

工作流程如下:

  1. 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务;
  2. 线程数等于corePoolSize之后,新任务加入LinkedBlockingQueue(无界阻塞队列)。因为最大线程数maximumPoolSize参数值等于corePoolSize,不会产生多余线程;
  3. 线程执行完任务之后会反复从LinkedBlockingQueue中获取任务来执行。
  • SingleThreadExecutor

单个worker线程的线程池

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

SingleThreadExecutor与FixedThreadPool的区别在于,maximumPoolSize和corePoolSize都设置成了1,其它参数都一样。

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

CachedThreadPool将corePoolSize设置为0,maximumPoolSize设置为无限大,同时使用了一个没有容量的工作队列SynchronousQueue。这个线程池没有固定的核心线程,而是根据需要创建新线程。

工作流程:

  1. 有新任务时,主线程执行SynchronousQueue.offer操作,空闲线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)操作,配对成功则将任务交给空闲线程执行;
  2. 当没有空闲线程时,上面的配对操作失败,此时会创建一个新线程来执行任务;
  3. 任务执行完毕后,空闲线程会等待60秒。60秒内如果有新任务,就立即执行,否则时间一过线程就终止。

线程池关闭

调用shutdown或者shutdownNow方法可关闭线程池。原理是遍历线程池中所有工作线程,调用interrupt方法来中断线程。

  • shutdown:将线程置为SHUTDOWN状态,不能接受新的任务,等待所有任务执行完毕;
  • shutdownNow:将线程置为STOP状态,不能接受新的任务,尝试去终止正在执行的恶任务;

这里涉及到ThreadPoolExecutor中定义的线程的五种状态

// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
  • RUNNING:接受新任务,处理任务;
  • SHUTDOWN:不接受新任务,但会把队列中任务处理完;
  • STOP:不接受新任务,不处理队列中的任务,并且终止正在处理的任务;
  • TIDYING:正在执行的任务和队列都为空,进入该状态,将要执行terminated();
  • TERMINATED:所有terminated()方法执行完毕,线程池彻底终止。

当队列和正在执行的任务都为空时,由SHUTDOWN转化为TIDYING;当正在执行的任务为空,由STOP转化为TIDYING。

本博客从线程池的原理介绍作为切入点,分析了线程池中尤为关键的组件:阻塞队列。同时分析了线程池的核心实现类ThreadPoolExecutor。以线程池的创建和关闭的思路,梳理了相关知识点,包括三种常用线程池介绍以及线程池五种状态。

Java线程池原理及分析的更多相关文章

  1. Java线程池使用和分析(二) - execute()原理

    相关文章目录: Java线程池使用和分析(一) Java线程池使用和分析(二) - execute()原理 execute()是 java.util.concurrent.Executor接口中唯一的 ...

  2. Java 线程池原理分析

    1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...

  3. Java线程池使用和分析(一)

    线程池是可以控制线程创建.释放,并通过某种策略尝试复用线程去执行任务的一种管理框架,从而实现线程资源与任务之间的一种平衡. 以下分析基于 JDK1.7 以下是本文的目录大纲: 一.线程池架构 二.Th ...

  4. java线程池原理

    在什么情况下使用线程池?     1.单个任务处理的时间比较短     2.将需处理的任务的数量大     使用线程池的好处:     1.减少在创建和销毁线程上所花的时间以及系统资源的开销     ...

  5. Java线程池原理解读

    引言 引用自<阿里巴巴JAVA开发手册> [强制]线程资源必须通过线程池提供,不允许在应用中自行显式创建线程. 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销 ...

  6. java线程池源码分析

    我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...

  7. 含源码解析,深入Java 线程池原理

    从池化技术到底层实现,一篇文章带你贯通线程池技术. 1.池化技术简介 在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能. 在编程领域,比较典型的池化技术有: 线程池.连接池.内存池 ...

  8. JAVA线程池原理与源码分析

    1.线程池常用接口介绍 1.1.Executor public interface Executor { void execute(Runnable command); } 执行提交的Runnable ...

  9. Java线程池原理与架构分析

    /** * 一.线程池:提供了一个线程队列,队列中保存着所有等待状态的线程.避免了创建与销毁额外开销,提高了响应速度 * 二.线程池的体系结构 * java.util.concurrent.Execu ...

随机推荐

  1. Django-当前菜单激活状态-模版 request | slice

    如何满足这个需求? 1. view中传递过来一个当前页面的参数标识,通过模版语言进行判断 {% if current_page == 'index' %}active{% endif %} # 每一个 ...

  2. 在Centos6上安装RabbitMQ的过程(有点坑)

    1.安装依赖环境 yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ ...

  3. 利用Python+pyecharts+tushare图形化展示股票历史财务信息

    在微信或其他平台上,经常能看到别人推荐股票,分析的头头是道,让自己懊恼于没有早点关注到这只股票,好像错失了几个亿.但是投资股票又忌讳听消息跟风,总不能看到别人推荐自己就无脑买入. 看到了一只股票,自己 ...

  4. Tensorflow学习笔记No.4.1

    使用CNN卷积神经网络(1) 简单介绍CNN卷积神经网络的概念和原理. 已经了解的小伙伴可以跳转到Tensorflow学习笔记No.4.2学习如和用Tensorflow实现简单的卷积神经网络. 1.C ...

  5. JWT安全性第1部分,创建令牌

    下载Demo Core 2.0 - 13.2 MB 下载Demo Core 1.2 - 14 MB 介绍 JWT (JSON Web Token)作为保护Web站点和REST服务的标准越来越流行.我将 ...

  6. Python 的映射数据类型有哪些?零基础小白入门学习必看

    1 映射类关系 Python 的 collections.abc 模块内拥有 Mapping 和 MutableMapping 这两个抽象基类,它们为 dict 和其他类似的类型提供了接口定义. mu ...

  7. Scala小记(一)

    Scala小记----初识Scala 一,什么是Scale? Scala是一门面向对象的,使用JVM运行的函数式编程语言,(函数式编程语言:指的就是那些将方法或者说是函数来作为参数 进行传递的编程语言 ...

  8. 每日一题 LeetCode 486. 预测赢家 【递推】【前缀和】【动态规划】

    题目链接 https://leetcode-cn.com/problems/predict-the-winner/ 题目说明 题解 主要方法:递推:动态规划:前缀和 解释说明: 求前缀和 pre_nu ...

  9. Python+Appium自动化测试(15)-使用Android模拟器(详细)

    做APP的UI自动化测试时,我们往往会使用真机跑自动化测试脚本,因为这样才是最真实的使用场景.但前期调试脚本的话,可以先使用模拟器,这样相对更加方便. 不推荐使用Android SDK里自带模拟器,太 ...

  10. day51 Pyhton 前端02

    内容回顾: 1.h1~h6:加粗,数字越大级别越小,自动换行 2.br:换行;hr:分割线; (特殊符号,空格) 3.p:与前边和后边内容之间有间距 4.a标签的href:本地文件连接;网络连接;锚链 ...