iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何彻底搞懂jdk8线程池
  • 489
分享到

如何彻底搞懂jdk8线程池

2023-06-25 12:06:20 489人浏览 八月长安
摘要

这篇文章将为大家详细讲解有关如何彻底搞懂jdk8线程池,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。顶层设计,定义执行接口Interface Executor(){ &n

这篇文章将为大家详细讲解有关如何彻底搞懂jdk8线程池,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

顶层设计,定义执行接口

Interface Executor(){    void execute(Runnable command);}

ExecutorService,定义控制接口

interface ExecutorService extends Executor{    }

如何彻底搞懂jdk8线程池

抽象实现ExecutorService中的大部分方法

abstract class AbstractExecutorService implements ExecutorService{    //此处把ExecutorService中的提交方法都实现了}

如何彻底搞懂jdk8线程池

我们看下提交中的核心

 public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        int c = ctl.get();        if (workerCountOf(c) < corePoolSize) { // ①             //核心线程数没有满就继续添加核心线程            if (addWorker(command, true)) // ②                return;            c = ctl.get();        }        if (isRunning(c) && workQueue.offer(command)) { // ③            int recheck = ctl.get();            if (! isRunning(recheck) && remove(command))// ④                reject(command); //⑦            else if (workerCountOf(recheck) == 0) // ⑤                //如果worker为0,则添加一个非核心worker,所以线程池里至少有一个线程                addWorker(null, false);// ⑥        }        //队列满了以后,添加非核心线程        else if (!addWorker(command, false))// ⑧            reject(command);//⑦    }

如何彻底搞懂jdk8线程池

这里就会有几道常见的面试题

1,什么时候用核心线程,什么时候启用非核心线程?

添加任务时优先使用核心线程,核心线程满了以后,任务放入队列中。只要队列不填满,就一直使用核心线程执行任务(代码①②)。

当队列满了以后开始使用增加非核心线程来执行队列中的任务(代码⑧)。

2,0个核心线程,2个非核心线程,队列100,添加99个任务是否会执行?

会执行,添加队列成功后,如果worker的数量为0,会添加非核心线程执行任务(见代码⑤⑥)

3,队列满了会怎么样?

队列满了,会优先启用非核心线程执行任务,如果非核心线程也满了,那就执行拒绝策略。

4,submit 和execute的区别是?

submit将执行任务包装成了RunnableFuture,最终返回了Future,executor 方法执行无返回值。

addworker实现

ThreadPoolExecutor extends AbstractExecutorService{    //保存所有的执行线程(worker)    HashSet<Worker> workers = new HashSet<Worker>();    //存放待执行的任务,这块具体由指定的队列实现    BlockingQueue<Runnable> workQueue;    public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler){    }    //添加执行worker    private boolean addWorker(Runnable firstTask, boolean core) {        //这里每次都会基础校验和cas校验,防止并发无法创建线程,        retry:        for(;;){            for(;;){                if (compareAndIncrementWorkerCount(c))                    break retry;                c = ctl.get();  // Re-read ctl                if (runStateOf(c) != rs)                    continue retry;            }        }        try{            //创建一个worker            w = new Worker(firstTask);            final Thread t = w.thread;            try{                //加校验,添加到workers集合中                workers.add(w);            }            //添加成功,将对应的线程启动,执行任务            t.start();        }finally{             //失败执行进行释放资源            addWorkerFailed(Worker w)         }             }    //Worker 是对任务和线程的封装    private final class Worker extends AbstractQueuedSynchronizer implements Runnable{        //线程启动后会循环执行任务        public void run() {            runWorker(this);        }    }    //循环执行    final void runWorker(Worker w) {        try{            while (task != null || (task = getTask()) != null) {                //执行前的可扩展点                beforeExecute(wt, task);                try{                     //执行任务                    task.run();                }finally{                    //执行后的可扩展点,这块也把异常给吃了                    afterExecute(task, thrown);                }            }            //这里会对执行的任务进行统计        }finally{             //异常或者是循环退出都会走这里             processWorkerExit(w, completedAbruptly);        }    }    //获取执行任务,此处决定runWorker的状态    private Runnable getTask() {        //worker的淘汰策略:允许超时或者工作线程>核心线程        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;        //满足淘汰策略且...,就返回null,交由processWorkerExit去处理线程        if ((wc > maximumPoolSize || (timed && timedOut))                && (wc > 1 || workQueue.isEmpty())) {                if (compareAndDecrementWorkerCount(c))                    return null;                continue;            }        // 满足淘汰策略,就等一定的时间poll(),不满足,就一直等待take()        Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();    }    //处理任务退出(循环获取不到任务的时候)    private void processWorkerExit(Worker w, boolean completedAbruptly) {        //异常退出的,不能调整线程数的        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted            decrementWorkerCount();                //不管成功或失败,都执行以下逻辑        //1,计数,2,减去一个线程        completedTaskCount += w.completedTasks;        //将线程移除,并不关心是否非核心        workers.remove(w);        //如果是还是运行状态        if (!completedAbruptly) {            //正常终止的,处理逻辑            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;            //核心线程为0 ,最小值也是1            if (min == 0 && ! workQueue.isEmpty())                min = 1;            //总线程数大于min就不再添加            if (workerCountOf(c) >= min)                return; // replacement not needed        }        //异常退出一定还会添加worker,正常退出一般不会再添加线程,除非核心线程数为0        addWorker(null, false);    }    }

如何彻底搞懂jdk8线程池

这里涉及到几个点:

1,任务异常以后虽然有throw异常,但是外面有好几个finally代码;

2,在finally中,进行了任务的统计以及worker移除;

3,如果还有等待处理的任务,最少添加一个worker(不管核心线程数是否为0)

这里会引申出来几个面试题:

1, 线程池中核心线程数如何设置?

cpu密集型:一般为核心线程数+1,尽可能减少cpu的并行;

IO密集型:可以设置核心线程数稍微多些,将IO等待期间的空闲cpu充分利用起来。

2,线程池使用队列的意义?

a)线程的资源是有限的,且线程的创建成本比较高;

b)  要保证cpu资源的合理利用(不能直接给cpu提一堆任务,cpu处理不过来,大家都慢了)

c) 利用了削峰填谷的思想(保证任务执行的可用性);

d) 队列过大也会把内存撑爆。

3,为什么要用阻塞队列?而不是非阻塞队列?

a) 利用阻塞的特性,在没有任务时阻塞一定的时间,防止资源被释放(getTask和processWorkExit);

b) 阻塞队列在阻塞时,CPU状态是wait,等有任务时,会被唤醒,不会占用太多的资源;

线程池有两个地方:

1,在execute方法中(提交任务时),只要工作线程为0,就至少添加一个Worker;

2,在processWorkerExit中(正常或异常结束时),只要有待处理的任务,就会增加Worker

所以正常情况下线程池一定会保证所有任务的执行。

我们在看下ThreadPoolExecutor中以下几个方法

public boolean prestartCoreThread() {        return workerCountOf(ctl.get()) < corePoolSize &&            addWorker(null, true);    }    void ensurePrestart() {        int wc = workerCountOf(ctl.get());        if (wc < corePoolSize)            addWorker(null, true);        else if (wc == 0)            addWorker(null, false);    }    public int prestartAllCoreThreads() {        int n = 0;        while (addWorker(null, true))            ++n;        return n;    }

确保了核心线程数必须是满的,这些方法特别是在批处理的时候,或者动态调整核心线程数的大小时很有用。

我们再看下Executors中常见的创建线程池的方法:

一、newFixedThreadPool 与newSingleThreadExecutor

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

特点:

1,核心线程数和最大线程数大小一样(唯一不同的是,一个是1,一个是自定义);

2,队列用的是LinkedBlockingQueue(长度是Integer.Max_VALUE)

当任务的生产速度大于消费速度后,很容易将系统内存撑爆。

二、 newCachedThreadPool 和

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

特点:最大线程数为Integer.MAX_VALUE

当任务提交过多时,线程创建过多容易导致无法创建

三、 newWorkStealingPool

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

这个主要是并行度,默认为cpu的核数。

四、newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {        return new ScheduledThreadPoolExecutor(corePoolSize);    }

封装起来的要么最大线程数不可控,要么队列长度不可控,所以阿里规约里也不建议使用Executors方法创建线程池。

ps:

生产上使用线程池,最好是将关键任务和非关键任务分开设立线程池,非关键业务影响关键业务的执行。

关于如何彻底搞懂jdk8线程池就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: 如何彻底搞懂jdk8线程池

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

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

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

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

下载Word文档
猜你喜欢
  • 如何彻底搞懂jdk8线程池
    这篇文章将为大家详细讲解有关如何彻底搞懂jdk8线程池,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。顶层设计,定义执行接口Interface Executor(){ &n...
    99+
    2023-06-25
  • 一篇文章彻底搞懂jdk8线程池
    这可能是最简短的线程池分析文章了。 顶层设计,定义执行接口 Interface Executor(){ void execute(Runnable command); ...
    99+
    2024-04-02
  • 一文彻底搞懂java多线程和线程池
    目录 什么是线程 一. Java实现线程的三种方式1.1、继承Thread类1.2、实现Runnable接口,并覆写run方法二. Callable接口...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(三)
    目录Java线程池线程池的优点线程池的6种创建方式创建单个线程池的作用是什么?线程池的第七种创建方式ThreadPoolExecutor的执行方式ThreadPoolExecutor...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(四)
    目录SimpleDateFormat非线程安全问题ThreadLocalThreadLocal的原理ThreadLocal常用方法ThreadLocal的初始化Inheritable...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(五)
    目录单例模式与多线程立即加载/饿汉模式延时加载/懒汉模式饿汉/懒汉对比阻塞队列的实现常见的锁策略乐观锁CASCAS在java中的应用CAS 的ABA问题ABA 问题的解决悲观锁独占锁...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(二)
    目录Java中的锁1.synchronized锁(jvm层的解决方案,也叫监视器锁)2.手动锁Locksynchronized锁synchronized使用场景1.使用synchro...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(一)
    目录Java多线程线程的创建线程常用方法线程的终止1.自定义实现线程的终止2.使用Thread的interrupted来中断3.Thraed.interrupted()方法和Thre...
    99+
    2024-04-02
  • Java基础:彻底搞懂java多线程
    目录进程与线程使用多线程的优势线程的状态创建线程线程中断总结进程与线程 进程 进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可...
    99+
    2024-04-02
  • 一文彻底搞懂Kotlin中的协程
    产生背景 为了解决异步线程产生的回调地狱 //传统回调方式 api.login(phone,psd).enquene(new Callback<User>(){ ...
    99+
    2024-04-02
  • 彻底搞懂MySQL存储过程和函数
    目录1.0  创建存储过程和函数1. 创建存储过程2. 创建存储函数2|0变量1. 定义变量2. 变量赋值3|0定义条件和处理程序1. 定义条件2. 定义处理程序4|0光标...
    99+
    2024-04-02
  • 一文彻底搞懂PHP进程信号处理
    本篇文章给大家带来了关于PHP的相关知识,其中主要详细介绍了PHP 进程信号处理,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。背景前两周老大给我安排了一个任务,写一个监听信号的包。因为我司的项目是运行在容器里边的,每次上线,需要重新打...
    99+
    2023-05-14
    进程 PHP
  • 一文彻底了解Android中的线程和线程池
    目录前言1.主线程和子线程2.Android中的线程形态2.1 AsyncTask2.2 AsyncTask的工作原理2.3 HandleThread2.4 IntentServic...
    99+
    2022-12-20
    Android 线程池 android线程机制 线程和线程池
  • 10分钟彻底搞懂微信小程序单页面应用路由
    单页面应用特征 「假设:」 在一个 web 页面中,有1个按钮,点击可跳转到站内其他页面。 「多页面应用:」 点击按钮,会从新加载一个html资源,刷新整个页面; 「单页面应用:」...
    99+
    2024-04-02
  • 一篇文章带你搞懂Java线程池实现原理
    目录1. 为什么要使用线程池2. 线程池的使用3. 线程池核心参数4. 线程池工作原理5. 线程池源码剖析5.1 线程池的属性5.2 线程池状态5.3 execute源码5.4 wo...
    99+
    2022-11-13
    Java线程池实现原理 Java线程池原理 Java线程池实现 Java线程池
  • 一盏茶的功夫帮你彻底搞懂JavaScript异步编程从回调地狱到async/await
     🎬 江城开朗的豌豆:个人主页  🔥 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 📘 1. 引言 📘 2. 使...
    99+
    2023-10-10
    javascript 开发语言 前端 原力计划
  • 如何彻底关闭qq小程序
    本文小编为大家详细介绍“如何彻底关闭qq小程序”,内容详细,步骤清晰,细节处理妥当,希望这篇“如何彻底关闭qq小程序”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。  第一种方式,就是你可以在网上找一个老版本下载重...
    99+
    2023-06-26
  • win8如何彻底关闭uac win8彻底关闭烦人的uac图解教程
    从Windows Vista以来,UAC的安全性控制就是使用者心中的痛,不仅造成操作上的不方便,对于真正的病毒威胁也没有足够的防御效果,因此很多读者都会选择将UAC关闭,不过这次Win 8可不一样喔!照以前的方法竟然无法...
    99+
    2023-05-20
    win8彻底关闭uac 彻底关闭uac win8 uac怎么关闭
  • springboot如何创建线程池
    这篇文章主要介绍springboot如何创建线程池,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!springboot创建线程池两种方式1.使用static代码块创建这样的方式创建的好处是当代码用到线程池的时候才会初始...
    99+
    2023-06-22
  • Android如何创建线程池
    在Android中,可以使用`ThreadPoolExecutor`类来创建线程池。下面是创建线程池的步骤:1. 导入所需的类:``...
    99+
    2023-09-12
    Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作