ThreadPoolExecutor

news2024/12/23 6:57:23

官方API解释线程池的好处:

(1)通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。

(2)对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。

一、Executor

       Executor是一个接口,跟线程池有关的基本都要跟他打交道。下面是常用的ThreadPoolExecutor的关系。

     Executor接口很简单,只有一个execute方法。

     ExecutorService是Executor的子接口,增加了一些常用的对线程的控制方法,之后使用线程池主要也是使用这些方法。

     AbstractExecutorService是一个抽象类。ThreadPoolExecutor就是实现了这个类。

二、ThreadPoolExecutor

     ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

1、ThreadPoolExecutor类的四个构造方法。

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

       构造方法参数讲解 

参数名作用
corePoolSize核心线程池大小
maximumPoolSize最大线程池大小
keepAliveTime线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
TimeUnitkeepAliveTime时间单位
workQueue阻塞任务队列
threadFactory新建线程工厂
RejectedExecutionHandler当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

 2、ThreadPoolExecutor类中有几个非常重要的方法

//主要是这四个方法
execute()
submit()
shutdown()
shutdownNow()

(1)execute()

   execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

   源码

 public void execute(Runnable command) {
         /*如果提交的任务为null  抛出空指针异常*/
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        /*如果当前的任务数小于等于设置的核心线程大小,那么调用addWorker直接执行该任务*/
        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)
                addWorker(null, false);
        }
        /*如果向队列中添加失败,那么就新开启一个线程来执行该任务*/
        else if (!addWorker(command, false))
            reject(command);
    }

它的主要意思就是:

    任务提交给线程池之后的处理策略,这里总结一下主要有4点
当线程池中的线程数小于corePoolSize 时,新提交的任务直接新建一个线程执行任务(不管是否有空闲线程) 
当线程池中的线程数等于corePoolSize 时,新提交的任务将会进入阻塞队列(workQueue)中,等待线程的调度 
当阻塞队列满了以后,如果corePoolSize < maximumPoolSize ,则新提交的任务会新建线程执行任务,直至线程数达到maximumPoolSize 
当线程数达到maximumPoolSize 时,新提交的任务会由(饱和策略)管理

(2)submit()

        submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。

(3)shutdown()和shutdownNow()

       如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

  如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

还有很多其他的方法:

  比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。

三.使用示例

public class Test {
    public static void main(String[] args) {  
        //核心线程数5,最大线程数10,阻塞队列采用ArrayBlockingQueue,做多排队5个
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));
         
        for(int i=0;i<15;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
            executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}


class MyTask implements Runnable {
   private int taskNum;
    
   public MyTask(int num) {
       this.taskNum = num;
   }
    
   @Override
   public void run() {
       System.out.println("正在执行task "+taskNum);
       try {
           Thread.currentThread().sleep(4000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("task "+taskNum+"执行完毕");
   }
}

运行结果:

正在执行task 0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
正在执行task 1
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
正在执行task 2
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行玩别的任务数目:0
正在执行task 3
正在执行task 4
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 10
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 11
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 12
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 13
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 14
task 0执行完毕
task 1执行完毕
task 2执行完毕
正在执行task 5
task 4执行完毕
正在执行task 8
正在执行task 6
正在执行task 7
task 3执行完毕
正在执行task 9
task 10执行完毕
task 13执行完毕
task 12执行完毕
task 11执行完毕
task 14执行完毕
task 5执行完毕
task 8执行完毕
task 6执行完毕
task 7执行完毕
task 9执行完毕

通过案例总结:

      当线程数小于核心线程数(5)时会创建新线程,如果要执行的线程大于5,就先把任务放入队列中,如果队列最大容量5已经满了,那会在创建线程,直到最大达到最大线程数10。

注意

      这里如果创建超过15个,比如将for循环中改成执行20个任务,就会抛出任务拒绝异常了。因为你的队列和最大线程数才15,如果有20个任务就会抛异常。

不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池

Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池
Executors.newFixedThreadPool(int);    //创建固定容量大小的缓冲池

      下面是这三个静态方法的具体实现;

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 static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

  newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

  newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

  newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

  实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

四、用线程池和不用线程池的区别是什么?

public class ThreadCondition implements Runnable {


@Test
public void testThreadPool(){
   Runtime run=Runtime.getRuntime();//当前程序运行对象
    run.gc();//调用垃圾回收机制,减少内存误差
    Long freememroy=run.freeMemory();//获取当前空闲内存
    Long protime=System.currentTimeMillis();
    for(int i=0;i<10000;i++){
      new Thread(new ThreadCondition()).start();
    }
    System.out.println("独立创建"+10000+"个线程需要的内存空间"+(freememroy-run.freeMemory()));
    System.out.println("独立创建"+10000+"个线程需要的系统时间"+(System.currentTimeMillis()-protime));


    System.out.println("---------------------------------");
    Runtime run2=Runtime.getRuntime();//当前程序运行对象
    run2.gc();//调用垃圾回收机制,减少内存误差
    Long freememroy2=run.freeMemory();//获取当前空闲内存
    Long protime2=System.currentTimeMillis();
   ExecutorService service=Executors.newFixedThreadPool(2);
    for(int i=0;i<10000;i++){
     service.execute(new ThreadCondition()) ;
    } 
    System.out.println("线程池创建"+10000+"个线程需要的内存空间"+(freememroy2-run.freeMemory()));
    service.shutdown();
   
    System.out.println("线程池创建"+10000+"个线程需要的系统时间"+(System.currentTimeMillis()-protime2));




}

@Override
public void run() {
//null
}

}

运行结果:

这也就说明了,线程池的优势。

参考

 1、Java并发编程:线程池的使用

 2、用线程池和不用线程池的区别是什么?

 3、线程池(ThreadPoolExecutor)源码分析之如何保证核心线程不被销毁的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1002588.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DNS (Domain Name System) 域名解析过程

一、域名与IP地址 通常情况下一台电脑都会有一个IPv4和IPv6地址&#xff08;由数字和字母组成&#xff0c;难以记忆&#xff09;&#xff0c;所以日常访问网站时我们通常都是采用输入域名&#xff08;方便记忆&#xff09;的方式来访问。 二、域名结构树 www 主机名bilibil…

HP惠普暗影精灵8P笔记本OMEN 17.3 英寸游戏本 17-ck1000(509V8AV)原厂Win11系统22H2

适用型号&#xff1a; 17-ck1000TX、17-ck1001TX、17-ck1002TX、17-ck1003TX、17-ck1004TX、17-ck1006TX、17-ck1007TX、17-ck1008TX 原装出厂系统自带所有驱动、出厂主题壁纸、Office办公软件、MyHP、惠普电脑管家、OMEN Command Center等预装程序 链接&#xff1a;https:/…

思科的简易配置

vlan 划分配置 1. 拓扑连接 2. 终端设备配置&#xff0c;vlan(v2, v3)配置&#xff0c;模式设置 然后设置交换机 fa 0/5 口为 trunk 模式&#xff0c;使得不同交换机同一 vlan 下 PC 可以互连 3.测试配置结果 用 ip 地址为 192.168.1.1 的主机(PC0)向同一 vlan(v2)下的 192.…

C高级 Linux中的文件相关指令

一、使用cut截取出Ubuntu用户的家目录&#xff0c;要求&#xff1a;不能使用":"作为分割 grep "ubuntu" /etc/passwd | cut -d "/" -n -f 2-3 | cut -c 1-11 二、思维导图 文件相关指令&#xff1a;

LayoutManager布局管理器

https://www.yuque.com/huangzhanqi/rhwoir/qdov2ax7lgeowyb4 之前&#xff0c;我们介绍了Component中有一个方法 setBounds() 可以设置当前容器的位置和大小&#xff0c;但是我们需要明确一件事&#xff0c;如果我们手动的为组件设置位置和大小的话&#xff0c;就会造成程序的…

免费敏捷看板工具做—用户反馈流程管理

一个产品在面向市场之后势必会收到来自各个渠道的用户反馈。好的反馈有利于公司品牌的建立&#xff0c;挑剔和负面反馈可以帮助我们了解到用户最真是的需求和使用场景&#xff0c;有利于优化产品提高服务。更加能够提升用户的粘性。 在进行用户反馈管理时&#xff0c;用户反馈…

芯片设计中的ECO是什么?

芯片设计中的ECO是什么&#xff1f;-腾讯云开发者社区-腾讯云 如标题所写&#xff0c;我们今天聊一聊IC设计种的ECO。在展开关于ECO的概念之前&#xff0c;我们先大致捋下数字IC设计的流程&#xff0c;有助于我们后面的讨论。 数字IC设计流程简述 1、确定项目需求 根据市场…

Diffusion-VITS:VITS与Grad-TTS的融合

Grad-TTS的核心思想&#xff1a;把diffusion当做一个postnet&#xff08;或者Plug-In&#xff09;用于特征增强。因此&#xff0c;它可以是一种通用模块应用于任何网络中&#xff0c;典型的作为FastSpeech2的后处理模块。这里&#xff0c;作者以VITS的SVC场景为例&#xff0c;提…

【LeetCode刷题篇零】一些基础算法知识和前置技能(上)

排序算法 O(N^2)的排序算法&#xff1a;冒泡、选择、插入 冒泡排序 冒泡的核心是两两比较&#xff0c;大数下沉&#xff0c;小数上浮&#xff0c;比较的轮数是数组的长度 N&#xff0c;每一轮比较的次数为 N - 当前轮的索引&#xff1a; 外层循环控制轮数 round: [1&#xf…

Python之线程(二)

一、线程同步和互斥锁 同一个资源,多人想用?排队啊! 现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。再比如,上厕所排队。 二、线…

ABB机器人6700系列更换SMB电池的具体方法和步骤

ABB机器人6700系列更换SMB电池的具体方法和步骤 前提: 当需要更换电池时,会显示低电量警告“38213 Battery charge low(电池电量低)” 注意事项: 为了避免机器人不同步,在保持机器人控制柜上电的情况下进行电池的更换,否则可能会导致机器人丢失位置数据。 SMB电池的具体…

AP2813平均电流型双路降压恒流驱动器

产品描述 AP2813 是一款双路降压恒流驱动器,高效率、外围简单、内置功率管&#xff0c;适用于 5-80V 输入的高精度降压 LED 恒流驱动芯片。内置功率管输出功率可达12W&#xff0c;电流 1.2A。AP2813 一路直亮&#xff0c;另外一路通过 MODE1 切换全亮&#xff0c;爆闪。AP281…

ERP辅助报价助力提高效率和准确性

一、ERP辅助报价的定义&#xff1a; ERP辅助报价是指通过企业资源计划系统提供的功能和工具&#xff0c;辅助企业进行报价流程的管理和执行。它涵盖了报价数据的收集、计算、分析和生成报价文件的全过程&#xff0c;以提高报价的准确性、效率和一致性。 二、ERP辅助报价的重要…

【2023年11月第四版教材】第12章《质量管理》(第二部分)

第12章《质量管理》&#xff08;第二部分&#xff09; 4 规划质量管理4.1 数据收集★★★4.2 数据分析★★★4.3 数据表现★★★4.4 质量管理计划★★★4.5 质量测量指标★★★ &#xff08;22下35&#xff09; 4 规划质量管理 组过程输入工具和技术输出计划1.规划质量管理1.项…

第一张 LVS负载均衡集群

一、LVS群集简介 1.群集的含义和应用场景 群集是为解决某个特定问题将多台计算机组合起来形成的单个系统&#xff0c;由多台主机构成&#xff0c;但对外只表现为一个整体&#xff0c;相当于一台大型计算机提供服务。 互联网应用中&#xff0c;随着站点对硬件性能、响应…

基于SSM的共享客栈管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

50etf期权的隐含波动率是什么意思?最通俗易懂的解释!

上证50ETF期权波动率是指衡量上证50ETF期权价格变动程度的指标&#xff0c;接触过期权的人应该都知道50etf期权的隐含波动率这个词。这是一个神秘的概念&#xff0c;初学者不容易理解&#xff0c;下文介绍50etf期权的隐含波动率是什么意思&#xff1f;隐波到底是什么&#xff1…

一分钟学会自制产品图册

​想自己制作精美的产品图册吗&#xff1f; 只需要一分钟&#xff01; 首先&#xff0c;你需要一款简单的制作工具&#xff0c;比如&#xff1a;FLBOOK在线制作电子杂志平台 1.点击开始创作选择套用模板制作 2.点击立即使用进入编辑页面 3.进入编辑页面后制作产品册时可以添加…

如何在 Vue.js 和 Nuxt.js 之间做出选择?

开篇 今天看了一位国外大佬的文章&#xff0c;主要是他对在项目中如何选择 Vue.js 或 Nuxt.js 的看法&#xff0c;欢迎大家在评论区发表看法&#xff0c;以下内容是他关于这个问题看法的整理&#xff0c;由于翻译水平有限&#xff0c;欢迎大家指正。 国外大佬的看法 Vue.js在开…

5.5 【MySQL】Page Header(页面头部)

在页中定义了一个叫Page Header的部分&#xff0c;它是页结构的第二部分&#xff0c;这个部分占用固定的56个字节&#xff0c;专门存储各种状态信息&#xff0c;具体如下&#xff1a; 名称 占用空间大小 描述 PAGE_N_DIR_SLOTS 2 字节 在页目录中的槽数量 PAGE_HEAP_TOP …