Java多线程研究04-线程池的使用(ThreadPoolExecutor详解)

Windows 2012 SYSVOL复制失败的处理 题外话:晓原理、知架构、查日志是排错的三件法宝今天在DC1上新建并链接了一个组策略对像,在客户端刷新时,却报下面的错误:“处理组策略失败。Windows无法应用组策略对象LDAP….”如下图所示:于是在客户端用\\domain.com访问,发现\\domain.com\SYSVOL\domain.com\policies下面没有上面报错的组策略对象。这是怎么一回事呢,pin... 阅读详情

为什么要使用线程池?

这里写图片描述

线程是一个操作系统概念。操作系统负责这个线程的创建、挂起、运行、阻塞和终结操作。而操作系统创建线程、切换线程状态、终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情。
另一方面,大多数实际场景中是这样的:处理某一次请求的时间是非常短暂的,但是请求数量是巨大的。这种技术背景下,如果我们为每一个请求都单独创建一个线程,那么物理机的所有资源基本上都被操作系统创建线程、切换线程状态、销毁线程这些操作所占用,用于业务请求处理的资源反而减少了。所以最理想的处理方式是,将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理本身。
另外,一些操作系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操作系统会变得不稳定。这也是我们要限制线程数量的原因。

线程池的基本使用方式

JAVA语言为我们提供了两种基础线程池的选择:ScheduledThreadPoolExecutor和ThreadPoolExecutor。它们都实现了ExecutorService接口(注意,ExecutorService接口本身和“线程池”并没有直接关系,它的定义更接近“执行器”,而“使用线程管理的方式进行实现”只是其中的一种实现方式)。这篇文章中,我们主要围绕ThreadPoolExecutor类进行讲解。

ThreadPoolExecutor类的使用方式:

public class PoolThreadSimple {


    public static void main(String[] args) throws Throwable {

        /*
         * corePoolSize:核心大小,线程池初始化的时候,就会有这么大
         * maximumPoolSize:线程池最大线程数
         * keepAliveTime:如果当前线程池中线程数大于corePoolSize。
         * 多余的线程,在等待keepAliveTime时间后如果还没有新的线程任务指派给它,它就会被回收
         * 
         * unit:等待时间keepAliveTime的单位
         * 
         * workQueue:等待队列。这个对象的设置是本文将重点介绍的内容
         * */
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new SynchronousQueue<Runnable>());
        for(int index = 0 ; index < 10 ; index ++) {
            poolExecutor.submit(new PoolThreadSimple.TestRunnable(index));
        }

    }

    /**
     * 这个就是测试用的线程
     */
    private static class TestRunnable implements Runnable {

        /**
         * 日志
         */
        private static Log LOGGER = LogFactory.getLog(TestRunnable.class);

        /**
         * 记录任务的唯一编号,这样在日志中好做识别
         */
        private Integer index;

        public TestRunnable(int index) {
            this.index = index;
        }

        /**
         * @return the index
         */
        public Integer getIndex() {
            return index;
        }

        @Override
        public void run() {
            /*
             * 线程中,就只做一件事情:
             * 等待60秒钟的事件,以便模拟业务操作过程
             * */
            Thread currentThread  = Thread.currentThread();
            TestRunnable.LOGGER.info("线程:" + currentThread.getId() + " 中的任务(" + this.getIndex() + ")开始执行===");
            synchronized (currentThread) {
                try {
                    currentThread.wait(60000);
                } catch (InterruptedException e) {
                    TestRunnable.LOGGER.error(e.getMessage(), e);
                }
            }

            TestRunnable.LOGGER.info("线程:" + currentThread.getId() + " 中的任务(" + this.getIndex() + ")执行完成");
        }

    }
}

下文中,将对线程池中的corePoolSize、maximumPoolSize、keepAliveTime、timeUnit、workQueue、threadFactory、handler参数和一些常用/不常用的设置项进行逐一讲解。

ThreadPoolExecutor逻辑结构和工作方式

在上面的代码中,我们创建线程池的时候使用了ThreadPoolExecutor中最简单的一个构造函数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

构造函数中需要传入的参数包括corePoolSize、maximumPoolSize、keepAliveTime、timeUnit和workQueue。要明确理解这些参数(和后续将要介绍的参数)的含义,就首先要搞清楚ThreadPoolExecutor线程池的逻辑结构。
[图片]
一定要注意一个概念,即存在于线程池中容器的一定是Thread对象,而不是你要求运行的任务(所以叫线程池而不叫任务池也不叫对象池);你要求运行的任务将被线程池分配给某一个空闲的Thread运行。
从上图中,我们可以看到构成线程池的几个重要元素:
等待队列:顾名思义,就是你调用线程池对象的submit()方法或者execute()方法,要求线程池运行的任务(这些任务必须实现Runnable接口或者Callable接口)。但是出于某些原因线程池并没有马上运行这些任务,而是送入一个队列等待执行。

核心线程:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是你创建线程时所设置的corePoolSize参数决定的。如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段)。

非核心线程:一旦任务数量过多(由等待队列的特性决定),线程池将创建“非核心线程”临时帮助运行任务。你设置的大于corePoolSize参数小于maximumPoolSize参数的部分,就是线程池可以临时创建的“非核心线程”的最大数量。这种情况下如果某个线程没有运行任何任务,在等待keepAliveTime时间后,这个线程将会被销毁,直到线程池的线程数量重新达到corePoolSize。

maximumPoolSize参数也是当前线程池允许创建的最大线程数量。那么如果设置的corePoolSize参数和设置的maximumPoolSize参数一致时,线程池在任何情况下都不会回收空闲线程。keepAliveTime和timeUnit也就失去了意义。

keepAliveTime参数和timeUnit参数也是配合使用的。keepAliveTime参数指明等待时间的量化值,timeUnit指明量化值单位。例如keepAliveTime=1,timeUnit为TimeUnit.MINUTES,代表空闲线程的回收阀值为1分钟。

说完了线程池的逻辑结构,下面我们讨论一下线程池是怎样处理某一个运行任务的。
1、首先可以通过线程池提供的submit()方法或者execute()方法,要求线程池执行某个任务。线程池收到这个要求执行的任务后,会有几种处理情况:
1.1、如果当前线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新的线程运行你的任务,无论之前已经创建的线程是否处于空闲状态。
1.2、如果当前线程池中运行的线程数量已经达到设置的corePoolSize大小,线程池会把你的这个任务加入到等待队列中。直到某一个的线程空闲了,线程池会根据设置的等待队列规则,从队列中取出一个新的任务执行。
1.3、如果根据队列规则,这个任务无法加入等待队列。这时线程池就会创建一个“非核心线程”直接运行这个任务。注意,如果这种情况下任务执行成功,那么当前线程池中的线程数量一定大于corePoolSize。
1.4、如果这个任务,无法被“核心线程”直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,且你没有为线程池设置RejectedExecutionHandler。这时线程池会抛出RejectedExecutionException异常,即线程池拒绝接受这个任务。(实际上抛出RejectedExecutionException异常的操作,是ThreadPoolExecutor线程池中一个默认的RejectedExecutionHandler实现:AbortPolicy,这在后文会提到)
2、一旦线程池中某个线程完成了任务的执行,它就会试图到任务等待队列中拿去下一个等待任务(所有的等待任务都实现了BlockingQueue接口,按照接口字面上的理解,这是一个可阻塞的队列接口),它会调用等待队列的poll()方法,并停留在哪里。
3、当线程池中的线程超过你设置的corePoolSize参数,说明当前线程池中有所谓的“非核心线程”。那么当某个线程处理完任务后,如果等待keepAliveTime时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,对所谓的“核心线程”和“非核心线程”是一视同仁的,直到线程池中线程的数量等于你设置的corePoolSize参数时,回收过程才会停止。

不常用的设置

在ThreadPoolExecutor线程池中,有一些不常用的甚至不需要的设置

allowCoreThreadTimeOut:

线程池回收线程只会发生在当前线程池中线程数量大于corePoolSize参数的时候;当线程池中线程数量小于等于corePoolSize参数的时候,回收过程就会停止。
allowCoreThreadTimeOut设置项可以要求线程池:将包括“核心线程”在内的,没有任务分配的任何线程,在等待keepAliveTime时间后全部进行回收:

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(1));

poolExecutor.allowCoreThreadTimeOut(true);

以下是设置前的效果:
[图片]
以下是设置后的效果:
[图片]

prestartAllCoreThreads

前文我们还讨论到,当线程池中的线程还没有达到你设置的corePoolSize参数值的时候,如果有新的任务到来,线程池将创建新的线程运行这个任务,无论之前已经创建的线程是否处于空闲状态。这个描述可以用下面的示意图表示出来:
[图片]
prestartAllCoreThreads设置项,可以在线程池创建,但还没有接收到任何任务的情况下,先行创建符合corePoolSize参数值的线程数:

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(1));

poolExecutor.prestartAllCoreThreads();
ADS RFpro 射频阻抗仿真步骤 导入器件后,在库里面选取器件的对应模型,如图所示我们选取10pf 系统自带的村田模型。选取网络上的器件,拖入到下面component Models。导入成功后,截取需要仿真的区域(不截取仿真时间会非常长),可以生成对应如下原理图,通过原理图继续调整仿真。图上找到需要仿真链路上的网络,右键设置为信号。的叠层信息,按照PCB板厂提供信息填写完整。选择仿真的频率范围和频点数,生成如下图的RFPro仿真界。,然后再把GND也添加到下面。按图设置截取需要仿真的部分。添加成功后 网络就有了,15 双击run开始仿真。 阅读详情

相关推荐

并发编程系列之十五多线程使用线程池ThreadPoolExecutor

线程池是经常面试问到的问题,主要有几种实现方式?具体参数代表的意思有些?多线程实现有哪一些? 这些会在文章总结出讲解主要关键干货。 示例1 ,线程池的实现与定时任务的线程实现: import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; imp...

java技术专家全栈成长之路 934

ThreadPoolExecutor实现多线程+实战

Java多线程实现,提高接口并发性和效率

酷爱编程的小猿同学 2065

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础 线程池使用 线程池综合案例 4.学员练习 5.线程池总结 概念介绍 1:什么是线程池 2:为什么使用线程池 3:线程池有哪些优势 什么是池 什么是线程池 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runna.

拉格朗日的学习笔记 12万+

Java多线程技术11——ThreadPoolExecutor类的使用1

ThreadPoolExecutor类可以非常方便的创建线程池对象,而不需要程序员设计大量的new实例化Thread相关的代码。

geminigoth的博客 1106

多线程ThreadPoolExecutor相关

希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放.根据ThreadPoolExecutor的构造方法,JDK提供了很多工厂方法来创建各种用途的线程池.整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。上述都是提供的提交任务的方法,根据不同的业务场景需求,选择对应的提交方法.任务数比较密集,但每个任务执行时间较短的情况。任务量一已知,相对耗时的任务。...

ABestRookie的博客 427

多线程ThreadPoolExecutor详解

为什么使用ThreadPoolExecutor来创建线程池

Java__EE小白成长之路 2280

Java多线程使用详解

Java多线程使用详解

Java之行 2737

线程池ThreadPoolExecutor使用

线程池ThreadPoolExecutor使用

北执南念的博客 1483

13.深入解析ThreadPoolExecutor线程池

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如Tomcat。线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。线程池有如下的优势降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。

sproutBoy的博客 1222

04Java并发 Java 线程池ThreadPoolExecutor

因为上一章节篇幅有限,所以我决定把一文秒懂 Java 线程池拆分为三篇文章单独介绍。本章节,我们就来看看 ThreadPoolExecutor

二师兄撒欢之地 619

线程池使用-ThreadPoolExecutor原理

线程池使用-ThreadPoolExecutor原理

weixin_46643875的博客 266

java线程池详解

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;CPU密集型:计算为主,线程数 ≈ CPU核数IO密集型:等待为主,线程数 ≈ CPU核数 × 2混合型:根据IO/CPU比例调整https://cloud.tencent.com/developer/article/2092854 【关于美团线程池参数配置的个人实现】

iov_a的博客 1023

线程池ThreadPoolExecutor使用

线程池ThreadPoolExecutor使用 32018.04.07 21:44:04字数841阅读61914 ThreadPoolExecutor提供了四个构造方法: ThreadPoolExecutor构造方法.png 我们以最后一个构造方法(参数最多的那个),对其参数进行解释: public ThreadPoolExecutor(int corePoolSi...

记录学习生活的点滴(Linux运维、java) 245

Java自定义线程池

一、Java语言本身也是多线程,回顾Java创建线程方式如下: 1、继承Thread类,(Thread类实现Runnable接口),来个类图加深印象。 2、实现Runnable接口实现无返回值、实现run()方法,啥时候run,黑话了。 3、实现Callable接口重写call()+FutureTask获取. public class CustomThread { public static void main(String[] args) { // 自定义线程 .

今天又是充满希望的一天 1万+

JavaEE初阶——多线程()——线程池

此篇文章与大家分享多线程的第六篇文章,关于线程池如果有不足的或者错误的请您指出!

m0_60963435的博客 1309

Java多线程

概述生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者-消费者问题能够让我们对多线程编程的理解更加深刻。所谓生产者消费者问题,实际上主要是包含了两类线程:一类是生产者线程:用于生产数据一类是消费者线程:用于消费数据为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为。

m0_61925586的博客 1983

理解Java与Android中线程线程池的原理和使用

Java中实现线程的2种方式 引入线程池 引入线程池的目的 认识ThreadPoolExecutor 开始使用线程池 newFixedThreadPool newSingleThreadExecutor newCachedThreadPool newScheduledThreadPool

linghuanyun的博客 1244

ThreadPoolExecutor 强大的线程池实现类

ThreadPoolExecutorJavajava.util.concurrent 包内的一个强大的线程池实现类。它允许你控制线程池的大小、任务队列的类型以及各种管理策略,从而可以高效地管理并发任务。ThreadPoolExecutor 是一个非常灵活且功能强大的工具,用于管理和调度多线程任务。通过理解其构造方法、常用方法以及自定义选项,你可以根据具体需求来有效地利用它。自定义线程工厂和拒绝策略。

逆风飞翔 359

零基础学Java第十九天之线程池

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面使线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。

qq_53720725的博客 2043

多线程线程池

多线程 创建线程的几种方式 继承Thread类重写run() 方法 创建Thread子类调用start()方法,因为start()方法是native方法,底层C++会执行run()方法 实现Runnable接口 new Thread(实现类实例,线程).start(); 通过Callable 和Future创建线程 创建Callable接口实现类,重写call()方法,call()方法是线程执行体,并且有返回值。 创建Callable接口实现类实例,使用FutureTask包装Callable

李小二的倔强 125

各地级市逐月降水量数据(1949-2023年).zip

数据名称:地级市-逐月降水量数据数据年份:1949-2023年相关数据省份、城市、城市代码、降水量(mm)

上一篇: Java多线程研究03-线程的基本操作(notify,notifyAll,interrupt,join,sleep)
下一篇: Java多线程研究05-ThreadPoolExecutor中workQueue、threadFactory和handle
zczpeng
博客等级 码龄15年 123粉丝 141原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值