Java 线程池ThreadPoolExecutor

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

 

Java 线程池 ThreadPoolExecutor.

 

JDK1.5 开始关于多线程加了很多特性。如:

ConcurrentHashMap: 放弃使用公用锁同步每一个方法,使用了更细化的锁机制,分离锁。对于大数据量的 HashMap 同步操作效率有了较大提升。

CopyOnWriteArrayList: 是同步 List 的一个并发替代品。其线程安全性来源于这样一个事实:只要有效的不可变对象被正确发布,那么访问它将不再需要更多的同步。在每次需要修改时它们会创建并重新发布一个信的容器拷贝,以此来实现可变性。

增加了 Callable Future Callable runnable 的一个可选替代。我们之前用的 Runnable 是不能返回状态的,而 Callable 是可以返回状态,返回的状态保存在泛型 Future<T> 里。

JDK1.5 里面还包含了一个重要的特性就是线程池。通过查看代码可以看出主要都是由大师 Doug Lea 来完成的。本文主要介绍线程池 ThreadPoolExecutor 的使用。

 

JDK1.5 的线程池由 Executor 框架提供。 Executor 框架将处理请求任务的提交和它的执行解耦。可以制定执行策略。在线程池中执行线程可以重用已经存在的线程,而不是创建新的线程,可以在处理多请求时抵消线程创建、消亡产生的开销。如果线程池过大,会导致内存的高使用量,还可能耗尽资源。如果过小,会由于存在很多的处理器资源未工作,对吞吐量造成损失。

由于内容较多没有一一研究,只看了较常用的 ThreadPoolExecutor ,所以在这里做个介绍。 ThreadPoolExecutor 的继承关系如下。

Executor->ExecutorService->AbstractExecutorService->ThreadPoolExecutor

核心池大小 (core pool size) 、最大池的大小 (maximum pool size) 、存活时间 (keep-alive time) 共同管理着线程的创建和销毁。

线程池类为 java.util.concurrent.ThreadPoolExecutor ,常用构造方法为:

/**

* Creates a new <tt> ThreadPoolExecutor </tt> with the given initial

* parameters.

*

* @param corePoolSize the number of threads to keep in the

* pool, even if they are idle.

* @param maximumPoolSize the maximum number of threads to allow in the

* pool.

* @param keepAliveTime when the number of threads is greater than

* the core, this is the maximum time that excess idle threads

* will wait for new tasks before terminating.

* @param unit the time unit for the keepAliveTime

* argument.

* @param workQueue the queue to use for holding tasks before they

* are executed. This queue will hold only the <tt> Runnable </tt>

* tasks submitted by the <tt> execute </tt> method.

* @param handler the handler to use when execution is blocked

* because the thread bounds and queue capacities are reached.

* @throws IllegalArgumentException if corePoolSize, or

* keepAliveTime less than zero, or if maximumPoolSize less than or

* equal to zero, or if corePoolSize greater than maximumPoolSize.

* @throws NullPointerException if <tt> workQueue </tt>

* or <tt> handler </tt> are null.

*/

public ThreadPoolExecutor( int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

RejectedExecutionHandler handler) {

this (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

Executors. defaultThreadFactory (), handler);

}

corePoolSize : 线程池维护线程的最少数量,哪怕是空闲的。

maximumPoolSize :线程池维护线程的最大数量。

keepAliveTime : 线程池维护线程所允许的空闲时间。

unit : 线程池维护线程所允许的空闲时间的单位。

workQueue : 线程池所使用的缓冲队列,改缓冲队列的长度决定了能够缓冲的最大数量。

拒绝任务:拒绝任务是指当线程池里面的线程数量达到 maximumPoolSize workQueue 队列已满的情况下被尝试添加进来的任务。

handler : 线程池对拒绝任务的处理策略。在 ThreadPoolExecutor 里面定义了 4 handler 策略,分别是

1. CallerRunsPolicy :这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。

2. AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。

3. DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。

4. DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。

 

一个任务通过 execute(Runnable) 方法被添加到线程池,任务就是一个 Runnable 类型的对象,任务的执行方法就是 Runnable 类型对象的 run() 方法。

当一个任务通过 execute(Runnable) 方法欲添加到线程池时,线程池采用的策略如下:

1. 如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

2. 如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。

3. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于 maximumPoolSize ,建新的线程来处理被添加的任务。

4. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于 maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。

处理任务的优先级为:

核心线程 corePoolSize 、任务队列 workQueue 、最大线程 maximumPoolSize ,如果三者都满了,使用 handler 处理被拒绝的任务。当线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。

理解了上面关于 ThreadPoolExecutord 的介绍,应该就基本能了解它的一些使用,不过在 ThreadPoolExocutor 里面有个关键的 Worker 类,所有的线程都要经过 Worker 的包装。这样才能够做到线程可以复用而无需重新创建线程。

同时 Executors 类里面有 newFixedThreadPool(),newCachedThreadPool() 等几个方法,实际上也是间接调用了 ThreadPoolExocutor ,不过是传的不同的构造参数。

下面通过一个例子的执行结果来理解

 

代码:

 

上面代码定义了一个 corePoolSize 2 maximumPoolSize 3 workerQuene 容量为 3 的线程池,也就是说在饱和状态下,只能容纳 6 个线程, 3 个是运行状态, 3 个在队列里面。同时代码又往线程池里面添加了 9 个线程,每个线程会运行 2 秒,这样必然会到达饱和状态。而饱和状态就涉及到对拒绝任务的处理策略,本处采用了 ThreadPoolExecutor.DiscardOldestPolicy() 运行结果如下:

put task@ 1

start ..task@ 1

put task@ 2

start ..task@ 2

put task@ 3

put task@ 4

put task@ 5

start ..task@ 3

put task@ 6

put task@ 7

put task@ 8

put task@ 9

start ..task@ 8

start ..task@ 9

采用 ThreadPoolExecutor.DiscardOldestPolicy() 运行结果如下:

put task@ 1

start ..task@ 1

put task@ 2

start ..task@ 2

put task@ 3

put task@ 4

put task@ 5

start ..task@ 3

put task@ 6

start ..task@ 6

start ..task@ 4

start ..task@ 5

put task@ 7

start ..task@ 7

put task@ 8

put task@ 9

start ..task@ 8

start ..task@ 9

采用 ThreadPoolExecutor.AbortPolicy() 运行结果如下:

put task@ 1

start ..task@ 1

put task@ 2

start ..task@ 2

put task@ 3

put task@ 4

put task@ 5

start ..task@ 3

put task@ 6

java.util.concurrent.RejectedExecutionException

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution( ThreadPoolExecutor.java:1477 )

at java.util.concurrent.ThreadPoolExecutor.reject( ThreadPoolExecutor.java:384 )

at java.util.concurrent.ThreadPoolExecutor.execute( ThreadPoolExecutor.java:867 )

at TestThreadPool.main( TestThreadPool.java:22 )

put task@ 7

java.util.concurrent.RejectedExecutionException

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution( ThreadPoolExecutor.java:1477 )

at java.util.concurrent.ThreadPoolExecutor.reject( ThreadPoolExecutor.java:384 )

at java.util.concurrent.ThreadPoolExecutor.execute( ThreadPoolExecutor.java:867 )

at TestThreadPool.main( TestThreadPool.java:22 )

put task@ 8

java.util.concurrent.RejectedExecutionException

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution( ThreadPoolExecutor.java:1477 )

at java.util.concurrent.ThreadPoolExecutor.reject( ThreadPoolExecutor.java:384 )

at java.util.concurrent.ThreadPoolExecutor.execute( ThreadPoolExecutor.java:867 )

at TestThreadPool.main( TestThreadPool.java:22 )

put task@ 9

java.util.concurrent.RejectedExecutionException

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution( ThreadPoolExecutor.java:1477 )

at java.util.concurrent.ThreadPoolExecutor.reject( ThreadPoolExecutor.java:384 )

at java.util.concurrent.ThreadPoolExecutor.execute( ThreadPoolExecutor.java:867 )

at TestThreadPool.main( TestThreadPool.java:22 )

start ..task@ 4

start ..task@ 5

采用 ThreadPoolExecutor.DiscardPolicy() 运行结果如下:

put task@ 1

start ..task@ 1

put task@ 2

start ..task@ 2

put task@ 3

put task@ 4

put task@ 5

start ..task@ 3

put task@ 6

put task@ 7

put task@ 8

put task@ 9

start ..task@ 4

start ..task@ 5

从运行结果可以看出不同的 Handler 策略对拒绝任务的处理方式。

目前还只偏重在使用层面的理解,底层细节的原理还有待日后学习,欢迎交流。

 

 

本文部分内容和例子参考了: http://blog.csdn.net/imicro/archive/2007/08/29/1763955.aspx

 

 

线程池7:线程池拒绝任务的两个时机;线程池拒绝任务的四种策略简介(AbortPolicy,DiscardPolicy,DiscardOldestPolicy,CallerRunsPolicy;); 说明: (1)本篇博客主要介绍线程池拒绝任务的两个时机;线程池拒绝任务的四种策略; 目录 一:拒绝任务的时机; (1)当线程池关闭后,新来的任务就会被拒绝; (2)当线程池的最大线程数、队列容量都有接线,并且都满了;继续添加任务时就会拒绝; 二:拒绝的策略; (1)AbortPolicy策略; (2)DiscardPolicy策略; (3)DiscardOldestPolicy策略; (4)CallerRunsPolicy策略; 一:拒绝任务的时机; (1)当线程池关闭后,. 阅读详情

相关推荐

当FutureTask遇上DiscardPolicy,有坑

今天来了解下当 FutureTask 遇上 DiscardPolicy 或 DiscardOldestPolicy 时容易掉的坑,然后分析分析问题产生的原因以及如何规避这类问题。

i余数的博客 1000

ThreadPoolExecutor

public class XmallQueryLogManager implements Runnable { private final static Logger logger = LoggerFactory.getLogger(XmallQueryLogManager.class); private static BlockingQueue<XmallQueryL...

dashalen的博客 791

JAVA 多线程】线程池 ThreadPoolExecutor使用详解

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略);线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作 线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这 个工作队列里。如果工作队列满了,则进入下个流程。

vdssdf的博客 2415

线程池有哪 4 种拒绝策略?

第一种拒绝策略是 AbortPolicy,这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。 第二种拒绝策略是 DiscardPolicy,这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。 第三种拒绝策略是 Dis.

m0_45223880的博客 7202

java线程池使用详解ThreadPoolExecutor使用示例

一 使用线程池的好处 二 Executor 框架 2.1 简介 2.2 Executor 框架结构(主要由三大部分组成) 1) 任务(Runnable /Callable) 2) 任务的执行(Executor) 3) 异步计算的结果(Future) 2.3 Executor 框架的使用示意图 三 (重要)ThreadPoolExecutor 类简单介绍 3.1 ThreadPoolExecutor 类分析 3.2 推荐使用 ThreadPoolExecutor 构造函数创建线程池 四 ..

xu_yong_lin的博客 5万+

Java 线程池 ThreadPoolExecutor

Java 线程池 ThreadPoolExecutor

26857259 995

Java 并发之线程池 ThreadPoolExecutor

顶层的接口,用来执行提交的 Runnable 任务。这个接口提供了一种将任务提交和任务运行机制(包括线程的使用、调度等)解耦的方式。总而言之,它是一个顶层接口。

xlh1191860939的博客 830

Java线程池ThreadPoolExecutor

(1)自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作。(1)线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲。,意味着:全部都是救急线程(60s 后可以回收),救急线程可以无限创建。时,会放入无界队列排队。(3)如果队列选择了有界队列,那么任务超过了队列大小时,会创建。并没有线程空闲,这时再加入任务,新加的任务会被加入。

YouPromise的博客 1500

Java 线程池ThreadPoolExecutor源码解读

线程池与其说他是复用Thread,不如说他是不断地向Thread中填充新的Runnable,然后调用run,减少了创建Thread的开销。这个是老生常谈的实现方法了,继承Thread的时候需要实现run,如果直接调用run的话是无法开启线程的。如果阻塞队列都放慢了,就会看非核心线程是否到了最大的线程数,如果达到了最大线程,就会执行拒绝策略。总的来说addWorker除了进行Worker的构建和添加到Workers之外,还进行了Worker中线程的启动,这块是真正执行我们定义的逻辑的地方。

allen_xu_2012_new的博客 970

java线程池ThreadPoolExecutor详解

创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等,threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize。线程池所允许的最大线程个数。

qq_37080455的博客 1062

Java 异步编程——常用线程池 ThreadPoolExecutor

默认情况下,只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。【注:每一个线程在获取锁的时候可能都会排队等待,如果在等待时间上,先获取锁的线程的请求一定先被满足,那么这个锁就是公平的。容量为0,不存储任何元素,即没有数据缓存的空间。

longool的博客 1393

JAVA多线程之ThreadPoolExecutor创建线程池

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors 返回的线程池对象的弊端如下:FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

lqr666的博客 3989

Java中的线程池ThreadPoolExecutor

Java中创建线程的方法有四种,分别为继承Thread类,实现Runnable接口,实现Callable接口和通过线程池。在实际开发中,我们最常用的就是线程池的方法,因为利用线程池我们可以重复利用线程,减少每次创建线程所带来的额外开销;同时使用线程池也方便我们维护管理线程信息;此外,线程池的存在也可以帮助我们避免出现因为代码bug导致的无限创建线程导致系统资源耗尽等问题。 Java中通过使用ThreadPoolExecutor来创建管理线程池,而Spring框架中使用ThreadPoolTaskExecu

qq_40076948的博客 1144

JAVA线程池ThreadPoolExecutor实现的四种线程池

线程池JAVA中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便。但是就会有一个问题,如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?这样Java线程池出现了。new Thread的弊端:a....

二一点 3368

java 线程池ThreadPoolExecutor

ThreadPoolExecutor线程池的处理流程和原理,更好地了解线程池的原理,线程池的种类(FixedThreadPool,CachedThreadPool,SingleThreadExecutor,ScheduledThreadPool) ​

fry3309的博客 3035

Java线程池ThreadPoolExecutor

ThreadPoolExecutor

leyili_s的博客 877
上一篇: Measure twice, cut once.
下一篇: ssh安装 和 scp命令 使用
abing37
博客等级 码龄22年 34粉丝 25原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值