1 - 线程池的基础用法

news2024/10/5 21:18:45

参考:线程池的基本用法 - 简书

1、为什么要用线程池?

在java中,开启线程的方式一般分为以下三种:

a. 继承Thread,实现其run方法;

b. 实现Runnabler接口,通过Thread来实现线程;

c. 实现 Callable 接口(创建线程较复杂)。

        但无论哪种方式,当线程执行完成后,生命周期就结束了。在Linux系统中,线程的创建是一种很耗资源和时间的工作,因此,实现线程的复用便可以极大的减小资源的消耗,因此,有了线程池的出现。

2.初始化线程池的参数问题

java内置的线程池:

构造方法(最完整的)

代码示例:

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

当核心线程数满了,并且任务队列也已经满的时候,此时最大线程数就要创建新的线程。

3、参数间的关系

        在上面七个参数中,我们重点要关注的是 参数1,2,5,7间的关系。
        corePoolSize,核心线程池大小。当我们添加的任务小于该值时,每添加一个任务,但会开启一个线程;一旦任务量大于了corePoolSize,则新添加的任务就会进入workQueue中,这是一个阻塞队列,当队列填满时,如果再添加任务,此时,新添加的任务就会触发新的线程的初始化(创建新的线程?)。此时持续添加任务,便会持续造成新的线程产生,但总共的线程不能超过maximumPoolSize。当总共开启的线程超过maximumPoolSize时,会便启动handler,对新任务进行拒绝。因此,workQueue在传入时,要设定一个大小,否则队列不满,则线程总数只会有corePoolSize个。
        如果线程空闲时间超过了keepAliveTime后,线程就会自动销毁。注意,这里销毁的线程不包括核心线程。

4、如何实现线程复用

        线程的生命周期在运行完run方法之后就结束了,因此,没办法将Thread拿过来重新用。想实现复用,只能让run方法无法结束,这时workQueue就起到了作用。
        在线程池中,所用的队列为阻塞队列。当队列中无数据时,当前线程就会阻塞,直到有数据进入,线程才会运行。因此当线程运行完一个任务后,去队列中获取下一个,如果无法取到新任务,则会阻塞,进而完成一个线程中运行多个任务,即复用的功能。

5、代码验证

package test1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @auther zhoulz
 * @description: 线程池的基础用法
 */
public class ThreadPoolTest {
    public static void main(String[] args){
        int corePoolSize = 2;
        int maximumPoolSize = 5;
        int keepAliveTime = 10 * 1000;
        int workQueueSize = 10;

        int taskSize = 4; // 输入不同的任务

        ExecutorService pool = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(workQueueSize));

        for (int i = 1; i <= taskSize; i++) {
            pool.execute(new MyThread(i));
        }
        pool.shutdown();
    }
}

class MyThread extends Thread{
    private int addNum;

    MyThread(int addNum){
        this.addNum = addNum;
    }

    @Override
    public void run() {
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "正在执行。。。" + addNum);
    }
}

        corePoolSize(2),maximumPoolSize(5),blockQueueSize(10)的值不变,我们测试taskSize不同时,输出的结果。

(1) taskSize = 2

结果:只创建了两个核心线程(pool-1-thread-1、pool-1-thread-2)。

(2)taskSize = 12, workQueue有数据,但不满或刚满

结果:还是只创建了两个核心线程(pool-1-thread-1、pool-1-thread-2),其他的任务均会进入队列中,当 thread1 和 thread2 运行完成后,进行复用执行其他任务。

(3)taskSize =15, taskSize = (maximumPoolSize+workQueueSize)阻塞队列填满,且线程正好开启到最大值

结论:可以看到任务1,2以及最后添加的13,14,15先运行了。这是因为,3到12之间的任务,会填入workQueue中,当workQueue填满时,还有任务进入,就会创建新的线程(这里新创建了3个,因为workQueue填满后多出来3个任务),运行后续加入的任务,直到所有线程数达到maximumPoolSize(5)。我们这种情况正好wrokQueue填满,而线程开启到最大值maximumPoolSize,任务刚刚与两个值一样。

(4)taskSize = 18 taskSize>(maximumPoolSize+workQueueSize), 任务超出最大线程数与队列等待数之和

结果:从上面可以看出,任务15之后的就看不到了且出现了异常,这说明超出的线程池的处理能力,如果我们传RejectedExecutionHandler handler,也就是拒绝策略,此时就会接到任务。

(并且程序一直阻塞着)

(5)corePoolSize=0,maximumPoolSize=3,blockQueueSize=90,taskSize=10

这是一个特殊情况,就是如果我们把corePoolSize置为0,且所有的任务不超过等待对列的大小会如何?按上面理的理解,因为队列不满,所以除了核心线程外,不会创建新线程,但此时corePoolSize为0?难道任务就一直在队列里无法执行吗?

 实际这种情况下,任务依然执行了,但线程只有一个。这个和我们设置 corePoolSize=1运行结果是一样的,原因呢?

 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)    //****注意这里  1
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

注意代码中标有注释的 1处,当corePoolSize==0时,会走到此处,引发创建线程的操作,所以当corePoolSize=0时,也会运行任务。关于 addWorker()代码的说明,可以参照 手撕ThreadPoolExecutor线程池源码,在Android OkHttp框架中,核心线程池就是0,且使用到的了个无容量的队列(相当于系统提供的newCachedThreadPool),有兴趣的可以去看一下。

6、参考值

        在使用中,corePoolSize可根据业务来定,另一参数maximumPoolSize则比较重要了,其具体值可根据任务类型来定:

a.CPU密集型
        此类的任务,特点为需要大量的使用cpu进行大量的计算,此时的最大线程数,最大值不能超过CPU核心数+1,之所以加1,考虑到cpu计算时,如果有数据在虚拟内存上,需要将其挪到内存上,此过程较为耗时,cpu在等待过程中,可能出现空闲,为了保证其不会空闲,所以+1。

b.IO密集型
        当任务中存在大量的网络读取或磁盘文件读取时,maximumPoolSize最大值不要超过 cpu核心数2。因为IO密集型,在等待网络数据或文件读取时,是不需要cpu的,采用DMS机制,此时cpu会空闲下来,因此有了2的操作。

c.cpu+io混合型
        如果任务中涉及到cpu计算以及IO操作,如果cpu计算与io操作所用的时间相差不大,则考虑将其拆分成两个任务;如果相差较大,一般是IO操作比较耗时,则可以忽略cpu任务,将其当成IO操作的任务即可。

7、4个参数的设计

1. 核心线程数(corePoolSize)   

        核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理。

2. 任务队列长度(workQueue)     

        任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200。

3. 最大线程数(maximumPoolSize)     

        最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个.

4. 最大空闲时间(keepAliveTime)     

        这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可。

注:上面4个参数的设置只是一般的设计原则,并不是固定的,用户也可以根据实际情况灵活调整!

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

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

相关文章

法的概念与大纲

一、法的概念 法是由国家制定或认可并由国家强制力保证实施的&#xff0c;反映特定物质生活条件所决定的统治阶级意志&#xff0c;以权利和义务为内容&#xff0c;以确认、保护和发展对统治阶级有利的社会关系和社会秩序为目的的规范系统。 二、法的特征 规范性 国家意志性 权…

微信恢复大师花了200多,套路一环接一环!

数据恢复本是一个科技进步的体现&#xff0c;让误操作导致的重要数据可以找回来。但是近年来数据恢复跟诈骗挂钩&#xff0c;数据恢复本是利好的事情&#xff0c;为什么会跟诈骗挂钩。究竟是什么原因呢&#xff1f;最近小编发现&#xff0c;百度推荐词出现&#xff1a;“微信恢…

Neural Network-神经网络算法本质

1. Word2vec~single CBOW算法推导BP word2vec主要实现方式有&#xff1a;skip-gram和CBOW。 CBOW的目的是根据上下文contextual words来预测当前中心词的概率&#xff0c;且上下文所有单词对当前中心词出现的概率影响权重是一样的&#xff0c;如在袋子中取词&#xff0c;取出…

痞子衡嵌入式:国内外串行NOR Flash厂商官网Cross Reference功能使用体验

大家好&#xff0c;我是痞子衡&#xff0c;是正经搞技术的痞子。今天痞子衡给大家讲的是国内外串行NOR Flash厂商官网Cross Reference功能。 串行 NOR Flash 是一个相对发展稳定的市场&#xff0c;目前全球市场约 90% 的份额被中国的三家厂商(Winbond华邦/MXIC旺宏/GigaDevice兆…

Java入门教程(26)——继承

文章目录1.继承的作用2.继承的关键字3.继承的特点4.实例5.instanceof 运算符继承是面向对象三大特征之一&#xff0c;继承可以让我们减少代码量&#xff0c;实现类的复用1.继承的作用 代码复用&#xff0c;更加容易实现类的扩展方便建模 2.继承的关键字 extends&#xff0c;…

网站都变灰了,几行代码可以实现

前言 这两天&#xff0c;我们经常逛的好多网站、app首页都变灰了&#xff0c;原因大家应该都知道了 网站变灰 ①B站 ②爱奇艺 ③ 腾讯视频 ④ csdn ⑤百度 怎么实现的呢&#xff1f; 难道这些网站开发商在网站开发的时候都准备一套灰色主题的UI么&#xff1f; 好奇心…

端水or信仰?ChatGPT“点评”Web3未来

近日&#xff0c;OpenAI发布了人工智能聊天机器人模型 ChatGPT&#xff0c;产品自11月30日发布到现在仅一周时间&#xff0c;就积累了上百万用户&#xff0c;广受大家好评。有人让它写代码&#xff0c;有人拿高考题目考验它&#xff0c;这些“难题”似乎都难不倒它&#xff0c;…

vue3 教程(上)

学 vue3 通过官方文档更详细&#xff0c;不过阅读本博客&#xff0c;可以更容易理解&#xff0c;且帮你速成&#xff01; 官方文档&#xff08;记得将API风格偏好切换为 组合式 否则你学的是vue2&#xff09; https://cn.vuejs.org/guide/introduction.html 学习前的准备 创建…

[synchronized ]关键字详解

目录 1.synchronized 特性 1.1互斥性 1.2内存刷新 1.3可重入 2.Java 标准库中的线程安全类 3.死锁问题 3.1 一个线程,一把锁 3.2 两个线程,两把锁 3.3 多个线程,多把锁 4.死锁的条件 1.synchronized 特性 1.1互斥性 synchronized 关键字会起到互斥效果,当某个线程执…

【C语言】字符串函数(一)

目录 一、strlen函数(计算字符串长度) 1、strlen函数的用途 2、strlen函数的使用 3、strlen函数的模拟实现 二、strcpy函数(字符串拷贝) 1、strcpy函数的用途 2、strcpy函数的使用 3、strcpy函数的模拟实现 三、strcat函数(字符串追加) 1、strcat函数的用途 2、strcat函数的使用…

【JavaSE】接口剩余内容

目录 1、接口使用实例 &#x1f4d5;逐步分析学生数组排序的写法 ✨思路&#xff1a; ✨代码实现 ✨弊端 &#x1f4d5;、改进 改进思路&#xff1a; 代码实现&#xff1a; 2、Cloneable接口和深拷贝 2.1、cloneable接口的作用 2.2、深拷贝和浅拷贝 2.2.1、浅拷贝 …

yum安装openldap2.4.44,并配置增量复制(Delta-syncrepl)环境

本文是在centos7环境下通过yum安装openldap2.4.44&#xff0c;并配置增量复制&#xff08;Delta-syncrepl&#xff09;环境 官网对于增量复制介绍&#xff1a;https://www.openldap.org/doc/admin24/replication.html#Delta-syncrepl%20replication Delta-syncrepl 是 syncrep…

ADI Blackfin DSP处理器-BF533的开发详解3:GPIO(含源代码)

我们从最基础的GPIO开始&#xff0c;先讲外设&#xff0c;这玩意不管是单片机&#xff0c;还是ARM&#xff0c;又或是FPGA&#xff0c;甚至SOC的芯片&#xff0c;都有GPIO&#xff0c;有共性&#xff0c;就好理解&#xff0c;让我们看看在ADI的DSP里头&#xff0c;GPIO是怎么一…

MySQL数据库学习(2)

一.MySQL语法书写规范&#xff1a; (1).SQL语句要以分号;结尾 在 RDBMS(关系型数据库)当中&#xff0c;SQL语句是逐条执行的&#xff0c;一条 SQL语句代表着数据库的一个操作。SQL语句是使用英文分号;结尾。 (2).SQL语句不区分大小写 SQL不区分关键字的大小写。例如&#xff0c…

CentOS7下mysql主从复制搭建

mysql安装 CentOS7安装MySql5.7完整教程_长头发的程序猿的博客-CSDN博客_centos7 mysql5.7安装 1、配置主机 1.1、修改my.cnf配置文件 vim /etc/my.cnf 最后一行添加&#xff1a; #主服务器唯一ID server-id1 #启用二进制日志 log-binmysql-bin #设置不要复制的数据库(可…

C++之虚函数

都说面向对象的三大特性是封装、继承、多态。C作为一门面向对象编程语言&#xff0c;肯定也是具备了面向对象的三大特性&#xff0c;那么在C中是如何实现多态的呢&#xff1f; 在C中是通过虚函数动态绑定的方式实现多态的。 虚函数与纯虚函数 首先我们来回顾一下虚函数&…

DAX:GROUPBY函数

DAX 中的 SUMMARIZE 函数功能强大&#xff0c;但同时也很难使用。 它可用于执行表之间的分组和连接&#xff0c;正如我们之前在分组数据一文中描述的那样。 不幸的是&#xff0c;它在计算聚合值时存在一些众所周知的性能问题。除了性能之外&#xff0c;SUMMARIZE 的计算限制是它…

ArrayList中的 subList 强转 ArrayList 导致异常

阿里《Java开发手册》上提过 [强制] ArrayList的sublist结果不可強转成ArrayList,否则会抛出ClassCastException异常&#xff0c;即java.util.RandomAccesSubList cannot be cast to java. util.ArrayList. 说明: subList 返回的是ArrayList 的内部类SubList, 并不是ArrayList …

一块RTX 3090加速训练YOLOv5s,时间减少11个小时,速度提升20%

作者&#xff5c;BBuf 很高兴为大家带来One-YOLOv5的最新进展&#xff0c;在《一个更快的YOLOv5问世&#xff0c;附送全面中文解析教程》发布后收到了很多算法工程师朋友的关注&#xff0c;十分感谢。 不过&#xff0c;可能你也在思考一个问题&#xff1a;虽然OneFlow的兼容性做…

SQL之substrate()函数用法

测试表字段查询如下&#xff1a; 测试在hive中截取前5位字符的第1种写法&#xff1a; 测试在hive中截取前5位字符的第2种写法&#xff1a; 测试在impala中截取前5位字符的第1种写法&#xff1a; 测试在impala中截取前5位字符的第2种写法&#xff1a; 结果&#xff1a; 1、在h…