Java线程池之ThreadPoolExecutor
前言
线程池可以提高程序的并发性能(当然是合适的情况下),因为对于没有线程的情况下,我们每一次提交任务都新建一个线程,这种方法存在不少缺陷:
1. 线程的创建和销毁的开销非常高,线程的创建需要时间,会延迟任务的执行,会消耗大量的系统资源。
2. 活跃的线程会消耗系统资源,而大量的空闲线程会占用许多内存,给垃圾回收器带来很大的压力,而大量线程在竞争CPU资源的时间还会产生气体的性能开销。
3. 系统在可创建的线程上存在一个限制,如果超过了这个限制,很可能抛出OOM。
我们不难发现,在一定范围下,增加线程能够提高系统的吞吐量,而当线程数超过合理值后,增加的线程反而会降低程序的执行速度。
Executor的引入
在JDK1.5中,我们引入了一个线程池框架,Executor框架,它能够分解任务的创建和执行过程。它包括Executor、ExecutorService、Callable等接口和Executors、ThreadPoolExecutor实现类等。
注意点
当然了看待事物需要辩证,是否使用了Executor框架就能很好地将复杂的任务执行解耦开来的。这边我们其实需要限制一下它的使用范围。
- 对于依赖型的任务而言,不是很适合使用线程池去操作,容易引发死锁,因为这种情况下我们需要小心维持这些任务的执行顺序,以保证不会触发死锁。
- 对于通过线程封闭实现线程安全的任务而言,使用单线程的Executor能够保证更安全的并发。
- 使用ThreadLocal的任务,因为线程池会复用线程,这将导致任务的ThreadLocal值失去意义(除非线程本地值受限于任务的生命周期)。
- 对响应时间敏感的任务,假设我们将一个执行时间很长的任务,或者多个执行时间很长的任务放到一个单线程的Executor中或者一个包含少量线程的线程池中,都会降低程序的响应速度。
合适的线程数
线程池在对处理同一类型的任务且相互独立的时候,能达到性能上的最佳,否则任务时长不一致很容易引起拥塞或是饥饿。
那线程池的线程数以多少为合适呢,对于计算密集型的任务而言,我们最好设置的线程数 = CPU数+1;(+1是为了保证当某个线程因为缺页故障或其他原因而暂停时,这个+1的线程能够确保CPU的时钟周期不会被浪费);而对于包含IO操作或者其他阻塞操作的任务时,由于线程不会一直执行,所以线程池的规模应该更大点。
线程池的使用
在实际使用过程中,我们一般借助于ThreadPoolExecutor来完成线程池的创建。ThreadPoolExecutor具有极好的扩展性,除了系统提供的四种常用的线程池,如CachedThreadPoolExecutor,FixedThreadPoolExecutor,SingleThreadPoolExecutor,ScheduledThreadExecutor。我们可以自定义线程池的构造函数,如线程池的基本线程数、最大线程数、线程池的超时时间(时间+时间单位),线程池的任务队列,线程池的线程工厂,线程池的饱和策略。
当然,我们在使用系统提供的四种线程池的时候,同样可以在后来修改线程池的配置。
线程的任务队列
LinkedBlockQueue:无界 CachedThreadPoolExecutor FixedThreadPoolExecutor的默认任务队列
ArrayBlockQueue:有界
PriorityQueue:优先级队列,有界 ScheduledThreadPoolExecutor的默认任务队列
SynchronousQueue:同步移交,队列容量为0,仅当有线程准备好时才会将任务放到队列中。
饱和策略
饱和策略的设置
对于有界队列而言,当有界队列被填满后,这时候我们需要用到线程的饱和策略,前面提到我们可以在后面配置线程池的设置,而饱和策略的修改就是通过ThreadPoolExecutor的setRejectedExecutionHandler方法来进行修改的(当某个任务呗提交到一个已经被关闭的Executor中,也会用到饱和策略)
饱和策略主要有以下几种:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
- AbortPolicy:中止策略,该策略将抛出未受检的RejectedExecution,调用者可以捕获这个异常根据实际需要编写直接的处理代码。
- CallerRunsPolicy:调用者运行,该策略不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者中,从而降低新任务的流量。它不会在线程池的某个线程中执行新提交的任务,而是调用了execute的线程中执行任务,当线程池的任务队列被填满后,下一个任务会在调用execute时在主线程中执行,由于执行任务需要一定时间,这段时间内主线程显然不能提交任务,从而保证线程池在这段时间内处理现有任务。
- DiscardPolicy:抛弃策略,舍弃任务。
- DiscardOldestPolicy:抛弃下一个将被执行的任务,然后尝试重新提交新的任务,(不适用于和优先队列合用,因为这样将抛弃的将是优先级最高的等待任务)
当然,我们可以使用Semaphore(信号量)来控制任务的提交速率。
线程工厂
我们可以通过自定义线程工厂,来实现我们自己的线程。具体示例如下所示:
自定义的线程工厂,记录了线程池的名字。
import java.util.concurrent.ThreadFactory; /**
* Created by DB on 2017/9/1.
*/
public class MyThreadFactory implements ThreadFactory {
private final String poolName; public MyThreadFactory(String poolName) {
this.poolName = poolName;
} @Override
public Thread newThread(Runnable r) {
return new MyAPPThread(r,poolName);
}
}
自定义的线程类:实现了指定线程的名字,设置自定义UncaughtExecptionHandler。
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger; /**
* Created by DB on 2017/9/1.
*/
public class MyAPPThread extends Thread {
public static final String DEFALUT_NAME = "MyAppThread";
private static volatile boolean debugLifecycle = false;
private static final AtomicInteger created = new AtomicInteger();
private static final AtomicInteger alive = new AtomicInteger();
private static final Logger log = Logger.getAnonymousLogger(); public MyAPPThread(Runnable r){
this(r,DEFALUT_NAME);
}
public MyAPPThread(Runnable r,String name){
super(r,name +"-"+created.incrementAndGet());
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.log(Level.SEVERE,"Uncaught in thread"+t.getName(),e);
}
});
} public void run(){
boolean debug = debugLifecycle;
if(debug){
log.log(Level.FINE,"Created" +getName());
}
try {
alive.incrementAndGet();
super.run();
}finally {
alive.decrementAndGet();
if(debug){
log.log(Level.FINE,"Exiting"+getName());
}
}
}
public static int getThreadsCreated(){
return created.get();
}
public static int getThreadsAlive(){
return alive.get();
}
public static boolean getDebug(){
return debugLifecycle;
}
public static void setDebug(Boolean b){
debugLifecycle=b;
}
}
扩展ThreadPoolExecutor
ThreadPoolExecutor是可扩展的,它提供了几个在子类中可以改写的方法:beforeExecute、afterExecute和terminated,通过实现这些方法我们可以实现扩展。
在执行任务的线程中将调用beforeExecute和afterExecute方法,在这些方法中我们可以添加日志、计时。监视或者统计信息收集的功能。无论任务是从run中正常返回还是抛出一个异常而返回,afterExecute都会被调用。而beforeExecute抛出RuntimeException时,任务将不被执行,afterExecute当然也不会被调用。
在线程池完成关闭操作时,会调用terminated,也就在所有任务都已经完成并且所有工作者线程也已经关闭后。在这个方法中我们可以实现Executor在其生命周期中分配的各种资源,还有执行发送通知、记录日志或者收集finalize统计信息等操作。
具体的框架如下:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
* Created by DB on 2017/9/1.
*/
public class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
} @Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
} @Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
} @Override
protected void terminated() {
super.terminated();
}
}
Java线程池之ThreadPoolExecutor的更多相关文章
- Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...
- 【Java 多线程】Java线程池类ThreadPoolExecutor、ScheduledThreadPoolExecutor及Executors工厂类
Java中的线程池类有两个,分别是:ThreadPoolExecutor和ScheduledThreadPoolExecutor,这两个类都继承自ExecutorService.利用这两个类,可以创建 ...
- 深入理解Java线程池:ThreadPoolExecutor
线程池介绍 在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理.如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题: 如果并发的请求数量非常多,但每个线 ...
- 学习java 线程池-1: ThreadPoolExecutor
1. Executor 该接口内只有一个接口方法 :该方法的目的就是执行指定的 Runnable (但会不会执行,或者会不会立马执行,则不一定.因为要取决于整个线程池的状态) Executor 中文的 ...
- Java线程池定制ThreadPoolExecutor官方定制实例
1.仍然先看构造方法:ThreadPoolExecutor构造方法 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,lon ...
- java 线程池 ExeutorService
Java线程池 ExecutorService 原文:https://blog.csdn.net/suifeng3051/article/details/49443835/ 本篇主要涉及到的是java ...
- 深入理解Java线程池:ScheduledThreadPoolExecutor
介绍 自JDK1.5开始,JDK提供了ScheduledThreadPoolExecutor类来支持周期性任务的调度.在这之前的实现需要依靠Timer和TimerTask或者其它第三方工具来完成.但T ...
- Java线程池 ExecutorService
一.ExecutorService介绍 ExecutorService是Java中对线程池定义的一个接口,它java.util.concurrent包中,在这个接口中定义了和后台任务执行相关的方法: ...
- 深入理解 Java 线程池
一.简介 什么是线程池 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务. 为什么要用线程池 如果并发请求数量很多,但每个线程执行的时间很短,就会出现频繁的创建 ...
随机推荐
- 51nod_1490: 多重游戏(树上博弈)
题目链接 该题实质上是一个树上博弈的问题.要定义四种状态--2先手必胜 1先手必败 3可输可赢 0不能控制 叶子结点为先手必胜态: 若某结点的所有儿子都是先手必败态,则该结点为先手必胜态: 若某结点的 ...
- hihocoder_1014: Trie树(Trie树模板题)
题目链接 #include<bits/stdc++.h> using namespace std; ; struct T { int num; T* next[]; T() { num=; ...
- STF,docker学习资料整理
- C#开发模式——单例模式
一.单例模式简介 单例模式是为保证一个对象,在程序运行的任何时刻只有一个实例存在,我们把这种实现方式称之为“单例模式”. 二.单例模式的核心 单例类只有一个实例存在 单例类提供一个全局访问点 单例类通 ...
- 【JAVASCRIPT】React学习-巧用 props 的 children 属性实现内容填充
背景 平常写组件,经常遇到需要获取内容放入组件内部的情形. 实现方法 我们有两种实现方式 1. 自定义 props render 的时候通过获取 this.props.content 填充到组件内部 ...
- ServletAPI的获取
Action中获取ServletAPI,三种方式 (1)通过ActionContext获取(只是获取并操作了域空间,并不是真正的ServletAPI对象) 在struts2框架中,通过Action的执 ...
- MySQL系列(四)---引擎
MySql中的引擎 目录 MySQL系列(一):基础知识大总结 MySQL系列(二):MySQL事务 MySQL系列(三):索引 引擎种类 FEDERATED MRG_MYISAM MyISAM BL ...
- Go语言学习笔记(七)杀手锏 Goroutine + Channel
加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 Goroutine Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Gorou ...
- 处理Oracle数据中的无效对象
今天还原了一份数据库(在服务器上没有无效对象),还原在本地之后有三十几个无效对象,当时很是郁闷,然后我发现还原之后的数据库中缺少表! 开始我怀疑Oracle数据库的还原功能,但是在我创建表的时候发现, ...
- 度度熊与邪恶大魔王 DP | 完全背包
Problem Description 度度熊为了拯救可爱的公主,于是与邪恶大魔王战斗起来. 邪恶大魔王的麾下有n个怪兽,每个怪兽有a[i]的生命值,以及b[i]的防御力. 度度熊一共拥有m种攻击方式 ...