前言

当项目中有频繁创建线程的场景时,往往会用到线程池来提高效率。所以,线程池在项目开发过程中的出场率是很高的。

那线程池是怎么工作的呢?它什么时候创建线程对象,如何保证线程安全...

什么时候创建线程对象

当实例化线程池对象时,并没有预先创建corePoolSize个线程对象,而是在调用executesubmit提交任务时,才会创建线程对象。

工作流程

public void execute(Runnable command) {
int c = ctl.get();
// 1. 如果核心线程数没有用完,则让核心线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 核心线程用完后,尝试添加到等待队列
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);
}
// 3. 等待队列满后,尝试创建临时线程执行任务;
// 如果创建临时线程失败,即达到了最大线程数,则采用拒绝策略处理
else if (!addWorker(command, false))
reject(command);
}

如何保存线程池状态

// 线程池采用状态压缩的思想,通过 32 个 bit 位来存储线程池状态和工作线程数量
// 其中,高 3 位存储线程池状态,低 29 位存储工作线程数量 // 表示工作线程数量位数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池最大容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 通过线程安全的 atomic 对象来存储线程池状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static int ctlOf(int rs, int wc) { return rs | wc; }

如何保证线程安全

1、对于一些需要线程共享的成员变量,通过volatile修饰

2、线程池状态和工作线程数的修改通过AtomicInteger类自带的线程安全方法实现

3、工作线程Worker通过线程不安全的HashSet存储,每次操作HashSet时都通过一个可重入锁ReentrantLock保证线程安全,ReentrantLock还用来保证访问一些线程不安全的变量,比如

/**
* Tracks largest attained pool size. Accessed only under
* mainLock.
*/
private int largestPoolSize; /**
* Counter for completed tasks. Updated only on termination of
* worker threads. Accessed only under mainLock.
*/
private long completedTaskCount;

4、Worker实现了AQS类,保证线程安全地操作工作线程WorkerWorker通过设置AQS#state来实现加锁和释放锁,当state == 0时,表示没有上锁;当state == 1时,表示上锁,通过CAS方式实现线程安全

protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
} public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }

内置线程池

1、SingleThreadPool

单线程池,只使用一个线程执行任务,阻塞队列LinkedBlockingQueue最大容量为Integer.MAX_VALUE

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

2、FixedThreadPool

固定大小的线程池,当创建的线程数量达到maxPoolSize,就不再创建线程,阻塞队列LinkedBlockingQueue最大容量为Integer.MAX_VALUE

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

3、CachedThreadPool

缓存线程池,核心线程数量为0,所以任务都通过创建临时线程来执行,临时线程空闲回收时间为60秒,当线程池空闲时,临时线程都会被回收,不耗费资源。

阻塞队列SynchronousQueue不会缓存任务,也就是说,新任务进来后会直接被调度执行,如果没有可用的线程了,则会创建新的线程,如果线程数达到maxPoolSize,即Integer.MAX_VALUE,就执行拒绝策略。

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

4、ScheduleThreadPool

定时调度线程池,通过DelayedWorkQueue

public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}

5、WorkStealingThreadPool

工作窃取线程池,当一个处理器忙时,空闲的处理器可以窃取该处理器后续的任务执行。通过ForkJoinPool实现,可设置支持的并行级别。

public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}

Java线程池工作原理的更多相关文章

  1. Java线程池的原理及几类线程池的介绍

    刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...

  2. Java 线程池的原理与实现 (转)

        最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知 ...

  3. Java线程池实现原理及其在美团业务中的实践

    本文转载自Java线程池实现原理及其在美团业务中的实践 导语 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供 ...

  4. 我眼中的java线程池实现原理

    最近在看java线程池实现方面的源码,在此做个小结,因为网上关于线程池源码分析的博客挺多的,我也不打算重复造轮子啦,仅仅用纯语言描述的方式做做总结啦! 个人认为要想理解清楚java线程池实现原理,明白 ...

  5. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  6. Java 线程池(ThreadPoolExecutor)原理分析与使用

    在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 使用线程池的好处 1.降低资源消耗 可以重复利用 ...

  7. Java 线程池(ThreadPoolExecutor)原理解析

    在我们的开发中“池”的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:<关于java多线程w ...

  8. Java线程池实现原理与技术(ThreadPoolExecutor、Executors)

    本文将通过实现一个简易的线程池理解线程池的原理,以及介绍JDK中自带的线程池ThreadPoolExecutor和Executor框架. 1.无限制线程的缺陷 多线程的软件设计方法确实可以最大限度地发 ...

  9. 深入浅出JAVA线程池使用原理1

    前言: Java中的线程池是并发框架中运用最多的,几乎所有需要异步或并发执行任务的程序都可以使用线程池,线程池主要有三个好处: 1.降低资源消耗:可以重复使用已经创建的线程降低线程创建和销毁带来的消耗 ...

随机推荐

  1. via浏览器和Alook浏览器插件安装

    via和Alook是Android和IOS上可以支持JS插件的浏览器,一些常用的插件可以在via-app.cn上找到.但总会有人会思考点击安装按钮的是怎样将JS脚本代码安装到浏览器的. 经过对页面代码 ...

  2. python虚拟环境之Pyenv

    一.windows下安装 1.使用命令安装 pip install pyenv-win --target %USERPROFILE%/.pyenv %USERPROFILE%/是具体的路径,例如 ## ...

  3. GE ifix 5.5中关于历史报警表的制作

    在关于污水处理厂项目实施过程中,按照业主要求,需要用到报警历史的查询功能,遂搜资料,整理在ifix5.5下如何实现报警历史的查询,经过一天的研究,以及多天的入坑,出坑,总算完成.现整理如下,供后来人参 ...

  4. DVWA靶场练习-Command Injection命令注入

    Command Injection 原理 攻击者通过构造恶意参数,破坏命令的语句结构,从而达到执行恶意命令的目的.

  5. JavaGUI画笔工具的使用

    JavaGUI画笔工具的使用 package GUI; import java.awt.*; public class TestPaint { public static void main(Stri ...

  6. CentOS7 快速安装配置mysql8.0

    因为这个项目是两台CentOS7虚拟机,一台当作 MySQL服务器,所以需要配置3306端口公开 参考教学视频:Java2020体系课 22周课 5-2~3 两节课 yum search mysql- ...

  7. k8s之数据存储-高级存储

    PV和PVC 前面已经学习了使用NFS提供存储,此时就会要求用户会搭建NFS系统,并且会在yaml配置nf's,由于k8s支持的存储系统有很多,要求客户全部掌握,显然不现实.为了能够屏蔽底层存储实现的 ...

  8. 【XSS-labs】Level 11-15

    Level 11 和level 10 差不多的页面,传参后查看页面源代码:依旧是第3个可以正常传参. 尝试level 10 的payload 发现 " 被实体化 可以打开控制台将第三个inp ...

  9. STM32—DMA存储器到外设

    DMA目录 DMA简介 DMA框图 DMA传输数据分析 1.传输的方向 2.传输的数量 3.传输的模式 代码部分 DMA初始化结构体 USART配置函数 DMA配置函数 主函数 DMA简介 DMA(D ...

  10. 【springboot】知识点总结

    [springboot 基础编] 01.SpringBoot>01 - 第一个应用–HelloWorld 02.SpringBoot>02 - 整合 MyBatis 03.SpringBo ...