iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Java线程池实现原理是什么及怎么使用
  • 598
分享到

Java线程池实现原理是什么及怎么使用

2023-07-04 12:07:20 598人浏览 安东尼
摘要

这篇文章主要讲解了“Java线程池实现原理是什么及怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java线程池实现原理是什么及怎么使用”吧!1. 为什么要使用线程池使用线程池通常由以

这篇文章主要讲解了“Java线程池实现原理是什么及怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java线程池实现原理是什么及怎么使用”吧!

1. 为什么要使用线程池

使用线程池通常由以下两个原因:

  • 频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程。

  • 使用线程池可以更容易管理线程,线程池可以动态管理线程个数、具有阻塞队列、定时周期执行任务、环境隔离等。

2. 线程池的使用

public class ThreadPoolDemo {    public static void main(String[] args) {        // 1. 创建线程池        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(                3,                3,                0L,                TimeUnit.MILLISECONDS,                new LinkedBlockingQueue<>(),                Executors.defaultThreadFactory(),                new ThreadPoolExecutor.AbortPolicy());              // 2. 往线程池中提交3个任务        for (int i = 0; i < 3; i++) {            threadPoolExecutor.execute(() -> {                System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");            });        }              // 3. 关闭线程池        threadPoolExecutor.shutdown();    }}

线程池的使用非常简单:

  • 调用new ThreadPoolExecutor()构造方法,指定核心参数,创建线程池。

  • 调用execute()方法提交Runnable任务

  • 使用结束后,调用shutdown()方法,关闭线程池。

再看一下线程池构造方法中核心参数的作用。

3. 线程池核心参数

线程池共有七大核心参数:

参数名称参数含义
int corePoolSize核心线程数
int maximumPoolSize最大线程数
long keepAliveTime线程存活时间
TimeUnit unit时间单位
BlockingQueue workQueue阻塞队列
ThreadFactory threadFactory线程创建工厂
RejectedExecutionHandler handler拒绝策略

1.corePoolSize 核心线程数

当往线程池中提交任务,会创建线程去处理任务,直到线程数达到corePoolSize,才会往阻塞队列中添加任务。默认情况下,空闲的核心线程并不会被回收,除非配置了allowCoreThreadTimeOut=true。

2.maximumPoolSize 最大线程数

当线程池中的线程数达到corePoolSize,阻塞队列又满了之后,才会继续创建线程,直到达到maximumPoolSize,另外空闲的非核心线程会被回收。

3.keepAliveTime 线程存活时间

非核心线程的空闲时间达到了keepAliveTime,将会被回收。

4.TimeUnit 时间单位

线程存活时间的单位,默认是TimeUnit.MILLISECONDS(毫秒),可选择的有:

  • TimeUnit.NANOSECONDS(纳秒)

  • TimeUnit.MICROSECONDS(微秒)

  • TimeUnit.MILLISECONDS(毫秒)

  • TimeUnit.SECONDS(秒)

  • TimeUnit.MINUTES(分钟)

  • TimeUnit.HOURS(小时)

  • TimeUnit.DAYS(天)

5.workQueue 阻塞队列

当线程池中的线程数达到corePoolSize,再提交的任务就会放到阻塞队列的等待,默认使用的是LinkedBlockingQueue,可选择的有:

  • LinkedBlockingQueue(基于链表实现的阻塞队列)

  • ArrayBlockingQueue(基于数组实现的阻塞队列)

  • SynchronousQueue(只有一个元素的阻塞队列)

  • PriorityBlockingQueue(实现了优先级的阻塞队列)

  • DelayQueue(实现了延迟功能的阻塞队列)

6.threadFactory 线程创建工厂

用来创建线程的工厂,默认的是Executors.defaultThreadFactory(),可选择的还有Executors.privilegedThreadFactory()实现了线程优先级。当然也可以自定义线程创建工厂,创建线程的时候最好指定线程名称,便于排查问题。

7.RejectedExecutionHandler 拒绝策略

当线程池中的线程数达到maximumPoolSize,阻塞队列也满了之后,再往线程池中提交任务,就会触发执行拒绝策略,默认的是AbortPolicy(直接终止,抛出异常),可选择的有:

  • AbortPolicy(直接终止,抛出异常)

  • DiscardPolicy(默默丢弃,不抛出异常)

  • DiscardOldestPolicy(丢弃队列中最旧的任务,执行当前任务)

  • CallerRunsPolicy(返回给调用者执行)

4. 线程池工作原理

线程池的工作原理,简单理解如下:

Java线程池实现原理是什么及怎么使用

  • 当往线程池中提交任务的时候,会先判断线程池中线程数是否核心线程数,如果小于,会创建核心线程并执行任务。

  • 如果线程数大于核心线程数,会判断阻塞队列是否已满,如果没有满,会把任务添加到阻塞队列中等待调度执行。

  • 如果阻塞队列已满,会判断线程数是否小于最大线程数,如果小于,会继续创建最大线程数并执行任务。

  • 如果线程数大于最大线程数,会执行拒绝策略,然后结束。

5. 线程池源码剖析

5.1 线程池的属性

public class ThreadPoolExecutor extends AbstractExecutorService {    // 线程池的控制状态,Integer长度是32位,前3位用来存储线程池状态,后29位用来存储线程数量    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));    // 线程个数所占的位数    private static final int COUNT_BITS = Integer.SIZE - 3;    // 线程池的最大容量,2^29-1,约5亿个线程    private static final int CAPACITY = (1 << COUNT_BITS) - 1;    // 独占,用来控制多线程下的并发操作    private final ReentrantLock mainLock = new ReentrantLock();    // 工作线程的集合    private final HashSet<Worker> workers = new HashSet<>();    // 等待条件,用来响应中断    private final Condition termination = mainLock.newCondition();    // 是否允许回收核心线程    private volatile boolean allowCoreThreadTimeOut;    // 线程数的历史峰值    private int largestPoolSize;        private volatile int corePoolSize;    private volatile int maximumPoolSize;    private volatile long keepAliveTime;    private final BlockingQueue<Runnable> workQueue;    private volatile ThreadFactory threadFactory;    private volatile RejectedExecutionHandler handler;}

线程池的控制状态ctl用来存储线程池状态和线程个数,前3位用来存储线程池状态,后29位用来存储线程数量。

设计者多聪明,用一个变量存储了两块内容。

5.2 线程池状态

线程池共有5种状态:

状态名称状态含义状态作用
RUNNING运行中线程池创建后默认状态,接收新任务,并处理阻塞队列中的任务。
SHUTDOWN已关闭调用shutdown方法后处于该状态,不再接收新任务,处理阻塞队列中任务。
STOP已停止调用shutdownNow方法后处于该状态,不再新任务,并中断所有线程,丢弃阻塞队列中所有任务。
TIDYING处理中所有任务已完成,所有工作线程都已回收,等待调用terminated方法。
TERMINATED已终止调用terminated方法后处于该状态,线程池的最终状态。

Java线程池实现原理是什么及怎么使用

5.3 execute源码

看一下往线程池中提交任务的源码,这是线程池的核心逻辑:

// 往线程池中提交任务public void execute(Runnable command) {    // 1. 判断提交的任务是否为null    if (command == null)        throw new NullPointerException();    int c = ctl.get();    // 2. 判断线程数是否小于核心线程数    if (workerCountOf(c) < corePoolSize) {        // 3. 把任务包装成worker,添加到worker集合中        if (addWorker(command, true))            return;        c = ctl.get();    }    // 4. 判断如果线程数不小于corePoolSize,并且可以添加到阻塞队列    if (isRunning(c) && workQueue.offer(command)) {        // 5. 重新检查线程池状态,如果线程池不是运行状态,就移除刚才添加的任务,并执行拒绝策略        int recheck = ctl.get();        if (!isRunning(recheck) && remove(command))            reject(command);        // 6. 判断如果线程数是0,就创建非核心线程(任务是null,会从阻塞队列中拉取任务)        else if (workerCountOf(recheck) == 0)            addWorker(null, false);    }    // 7. 如果添加阻塞队列失败,就创建一个Worker    else if (!addWorker(command, false))        // 8. 如果创建Worker失败说明已经达到最大线程数了,则执行拒绝策略        reject(command);}

execute方法的逻辑也很简单,最终就是调用addWorker方法,把任务添加到worker集合中,再看一下addWorker方法的源码:

// 添加workerprivate boolean addWorker(Runnable firstTask, boolean core) {    retry:    for (; ; ) {        int c = ctl.get();        int rs = runStateOf(c);        // 1. 检查是否允许提交任务        if (rs >= SHUTDOWN &&                !(rs == SHUTDOWN &&                        firstTask == null &&                        !workQueue.isEmpty()))            return false;        // 2. 使用死循环保证添加线程成功        for (; ; ) {            int wc = workerCountOf(c);            // 3. 校验线程数是否超过容量限制            if (wc >= CAPACITY ||                    wc >= (core ? corePoolSize : maximumPoolSize))                return false;            // 4. 使用CAS修改线程数            if (compareAndIncrementWorkerCount(c))                break retry;            c = ctl.get();            // 5. 如果线程池状态变了,则从头再来            if (runStateOf(c) != rs)                continue retry;        }    }    boolean workerStarted = false;    boolean workerAdded = false;    Worker w = null;    try {        // 6. 把任务和新线程包装成一个worker        w = new Worker(firstTask);        final Thread t = w.thread;        if (t != null) {            // 7. 加锁,控制并发            final ReentrantLock mainLock = this.mainLock;            mainLock.lock();            try {                // 8. 再次校验线程池状态是否异常                int rs = runStateOf(ctl.get());                if (rs < SHUTDOWN ||                        (rs == SHUTDOWN && firstTask == null)) {                    // 9. 如果线程已经启动,就抛出异常                    if (t.isAlive())                        throw new IllegalThreadStateException();                    // 10. 添加到worker集合中                    workers.add(w);                    int s = workers.size();                    // 11. 记录线程数历史峰值                    if (s > largestPoolSize)                        largestPoolSize = s;                    workerAdded = true;                }            } finally {                mainLock.unlock();            }            if (workerAdded) {                // 12. 启动线程                t.start();                workerStarted = true;            }        }    } finally {        if (!workerStarted)            addWorkerFailed(w);    }    return workerStarted;}

方法虽然很长,但是逻辑很清晰。就是把任务和线程包装成worker,添加到worker集合,并启动线程。

5.4 worker源码

再看一下worker类的结构:

private final class Worker        extends AbstractQueuedSynchronizer        implements Runnable {    // 工作线程    final Thread thread;    // 任务    Runnable firstTask;    // 创建worker,并创建一个新线程(用来执行任务)    Worker(Runnable firstTask) {        setState(-1);        this.firstTask = firstTask;        this.thread = getThreadFactory().newThread(this);    }}

5.5 runWorker源码

再看一下run方法的源码:

// 线程执行入口public void run() {    runWorker(this);}// 线程运行核心方法final void runWorker(Worker w) {    Thread wt = Thread.currentThread();    Runnable task = w.firstTask;    w.firstTask = null;    w.unlock();    boolean completedAbruptly = true;    try {        // 1. 如果当前worker中任务是null,就从阻塞队列中获取任务        while (task != null || (task = getTask()) != null) {            // 加锁,保证thread不被其他线程中断(除非线程池被中断)            w.lock();            // 2. 校验线程池状态,是否需要中断当前线程            if ((runStateAtLeast(ctl.get(), STOP) ||                    (Thread.interrupted() &&                            runStateAtLeast(ctl.get(), STOP))) &&                    !wt.isInterrupted())                wt.interrupt();            try {                beforeExecute(wt, task);                Throwable thrown = null;                try {                    // 3. 执行run方法                    task.run();                } catch (RuntimeException x) {                    thrown = x;                    throw x;                } catch (Error x) {                    thrown = x;                    throw x;                } catch (Throwable x) {                    thrown = x;                    throw new Error(x);                } finally {                    afterExecute(task, thrown);                }            } finally {                task = null;                w.completedTasks++;                // 解锁                w.unlock();            }        }        completedAbruptly = false;    } finally {        // 4. 从worker集合删除当前worker        processWorkerExit(w, completedAbruptly);    }}

runWorker方法逻辑也很简单,就是不断从阻塞队列中拉取任务并执行。

再看一下从阻塞队列中拉取任务的逻辑:

// 从阻塞队列中拉取任务private Runnable getTask() {    boolean timedOut = false;    for (; ; ) {        int c = ctl.get();        int rs = runStateOf(c);        // 1. 如果线程池已经停了,或者阻塞队列是空,就回收当前线程        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {            decrementWorkerCount();            return null;        }        int wc = workerCountOf(c);        // 2. 再次判断是否需要回收线程        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;        if ((wc > maximumPoolSize || (timed && timedOut))                && (wc > 1 || workQueue.isEmpty())) {            if (compareAndDecrementWorkerCount(c))                return null;            continue;        }        try {            // 3. 从阻塞队列中拉取任务            Runnable r = timed ?                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :                    workQueue.take();            if (r != null)                return r;            timedOut = true;        } catch (InterruptedException retry) {            timedOut = false;        }    }}

感谢各位的阅读,以上就是“Java线程池实现原理是什么及怎么使用”的内容了,经过本文的学习后,相信大家对Java线程池实现原理是什么及怎么使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: Java线程池实现原理是什么及怎么使用

本文链接: https://www.lsjlt.com/news/345141.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java线程池实现原理是什么及怎么使用
    这篇文章主要讲解了“Java线程池实现原理是什么及怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java线程池实现原理是什么及怎么使用”吧!1. 为什么要使用线程池使用线程池通常由以...
    99+
    2023-07-04
  • Java线程池实现原理是什么
    这篇文章主要讲解了“Java线程池实现原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java线程池实现原理是什么”吧!一、线程池参数corePoolSize(必填):核心线程数。m...
    99+
    2023-06-28
  • Java中线程池的实现原理是什么
    这篇文章给大家介绍Java中线程池的实现原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。线程池是什么?我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销。所以基于线程的复用,就...
    99+
    2023-05-31
    java 线程池 ava
  • Java中实现线程池的原理是什么
    Java中实现线程池的原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。01.***制线程的缺点多线程的软件设计方法确实可以***限度地发挥多核处理器的计算能力,提高生产...
    99+
    2023-06-16
  • java线程池使用及原理面试题
    目录引导语1、说说你对线程池的理解?2、ThreadPoolExecutor、Executor、ExecutorService、Runnable、Callable、FutureTas...
    99+
    2024-04-02
  • Java线程池工作原理和使用方法是什么
    这篇文章主要介绍“Java线程池工作原理和使用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java线程池工作原理和使用方法是什么”文章能帮助大家解决问题。1. 为什么要使用线程池使用线程...
    99+
    2023-07-04
  • SpringBoot线程池和Java线程池的使用和实现原理解析
    目录SpringBoot线程池和Java线程池的用法和实现原理使用默认的线程池方式一:通过@Async注解调用方式二:直接注入 ThreadPoolTaskExecutor...
    99+
    2023-05-15
    SpringBoot线程池和Java线程池用法 SpringBoot线程池
  • java线程池的实现原理、优点与风险、以及4种线程池实现
    为什么需要线程池 我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口。但是我们创建这两种线程...
    99+
    2023-02-18
    java 线程池的实现原理 java 线程池优点与风险 java 4种线程池实现 java 配置线程池大小配置 java 线程池的实现原理
  • android线程池的原理是什么
    Android线程池的原理是通过管理和调度线程来实现并发执行任务的机制。线程池主要由线程池管理器、工作队列和线程池的线程组成。线程池...
    99+
    2023-09-23
    android
  • Java线程池实现原理总结
    目录一、线程池参数二、线程池执行流程三、四种现成的线程池要理解实现原理,必须把线程池的几个参数彻底搞懂,不要死记硬背 一、线程池参数 1、corePoolSize(必填):核心线程数...
    99+
    2024-04-02
  • 线程池的原理和作用是什么
    这篇文章主要讲解了“线程池的原理和作用是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“线程池的原理和作用是什么”吧!一、为什么需要线程池在实际使用中,线...
    99+
    2024-04-02
  • 详解Java线程池的使用及工作原理
    目录一、什么是线程池?二、线程池要解决什么问题?三、线程池的使用四、常用阻塞队列五、线程工厂六、拒绝策略七、线程池的执行逻辑八、execute()方法九、执行流程一、什么是线程池? ...
    99+
    2024-04-02
  • java线程池实现的方法是什么
    Java中线程池的实现方法是通过使用java.util.concurrent包中的ThreadPoolExecutor类来创建和管理...
    99+
    2023-10-11
    java
  • 线程池的工作原理是什么
    这篇文章主要讲解了“线程池的工作原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“线程池的工作原理是什么”吧!线程池的自我介绍我是一个线程池(Thre...
    99+
    2024-04-02
  • SpringBoot线程池和Java线程池怎么使用
    这篇文章主要介绍“SpringBoot线程池和Java线程池怎么使用”,在日常操作中,相信很多人在SpringBoot线程池和Java线程池怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SpringB...
    99+
    2023-07-06
  • 线程池是什么?线程池(ThreadPoolExecutor)使用详解
    点一点,了解更多https://www.csdn.net/ 本篇文章将详细讲解什么是线程池,线程池的参数介绍,线程池的工作流程,使用Executors创建常见的线程池~~~ 目录 点一点,了解更多 文章目录 一、线程池的概念 1.1线...
    99+
    2023-09-03
    java 数据结构 jvm 面试 java-ee
  • Python中的进程池和线程池的适用场景和实现原理是什么?
    Python中的进程池和线程池的适用场景和实现原理是什么?引言:在编写程序时,为了提高执行效率,经常会使用并发编程来同时执行多个任务。Python提供了进程池和线程池这两种用于并发处理任务的工具。本文将详细介绍进程池和线程池的适用场景和实现...
    99+
    2023-10-22
    线程池 实现原理 进程池 适用场景
  • java中什么是线程池
    本篇文章为大家展示了java中什么是线程池,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4. ...
    99+
    2023-06-14
  • Java线程池的本质以及作用是什么
    Java线程池的本质以及作用是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。熟悉java多线程的 朋友一定十分了解java的线程池,jdk中的核心实现类为j...
    99+
    2023-06-17
  • 深入理解Java编程线程池的实现原理
    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间...
    99+
    2023-05-30
    java 线程池 ava
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作