Java多线程学习之线程池源码详解
0、使用线程池的必要性
在生产环境中,如果为每个任务分配一个线程,会造成许多问题:
- 线程生命周期的开销非常高。线程的创建和销毁都要付出代价。比如,线程的创建需要时间,延迟处理请求。如果请求的到达率非常高并且请求的处理过程都是轻量级的,那么为每个请求创建线程会消耗大量计算机资源。
- 资源消耗。 活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于处理器数量,那么有些线程会闲置。闲置的线程会占用内存,给垃圾回收器带来压力,大量线程在竞争CPU时,还会产生其他的性能开销。
- 稳定性。 如果线程数量过大,可能会造成OutOfMemory异常。
1、 Java中的ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor
类是线程池中最核心的类,因此如果要深入理解Java中的线程池,必须深入理解这个类。我们来看一下ThreadPoolExecutor类的源码。ThreadPoolExecutor类中提供了四个构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
} 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数介绍:
- corePoolSize:核心池大小。在创建线程池后,默认情况下,线程池中没有任何线程,而是等待任务到来材创建线程去执行任务。也可以提前调用
prestartAllCoreT hreads()
或者prestartCoreThread()
方法提前创建corePoolSize个线程或是一个线程。默认情况下,线程池在创建后线程数量为0,当有任务提交时,会创建线程,当线程达到corePoolSize个后,新提交的的任务会放到缓存队列中存放。 - maximumPoolSize:线程池的最大线程数量,表示在线程池中最多可以创建多少个线程。当缓存队列已满时候,新提交的任务会创建新的线程执行,直到线程池中达到maximumPoolSize个线程。
- keepAliveTime:表示线程没有任务执行时最多存活时间。默认情况下,只有当线程池的线程数量大于corePoolSize个时才会生效,就是说多余corePoolSize个的其他线程存活时间会受限,也可以调用
allowCoreThreadTimeOut(true)
方法设置线程池中所有的线程存活时间限制。 - unit:存活时间的单位,有如下单位:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
- workQueue:阻塞队列,用于暂时存放任务,有如下几种:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
- threadFactory:线程工厂,指定创建线程的策略。
- handler:当任务无法被及时处理和存放时候,进行处理的策略。比如说,线程池已满并且阻塞队列已满,新提交的任务需要被进行其他处理。有如下的处理方案:
- “终止(Abort)” 策略
ThreadPoolExecutor.AbortPolicy
:默认的饱和策略,该策略抛出未检查的RejectedExecutionException 异常,调用者需要处理此异常。ThreadPoolExecutor.DiscardPolicy
:也是丢弃任务,但是不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy
:丢弃下一个即将被执行的任务,然后尝试重新提交此任务。如果工作队列设一个优先队列,那么将会抛弃(Discard) 优先级最高的任务,显然,这是很不合理的。
- “调用者运行(Caller-Runs)”策略
ThreadPoolExecutor.CallerRunsPolicy
:该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者,有调用者线程来执行此线程。由于调用者线程执行该任务需要一定的时间,所以在该期间内,调用者线程无法接受其他的任务,为线程池中的线程争取执行时间。在WEB 服务器中,此期间到达的请求会被保存在TCP层的队列中而不是在应用程序的队列中。如果持续过载,TCP层队列被堆满,他会开始抛弃请求。这样,如果服务器过载,压力会向外蔓延:从线程池的消息队列到应用程序再到TCP层,最终到达客户端,导致服务器在高负载情况下性能的缓慢降低。
2、线程池的状态
在ThreadPoolExecutor 中定义了一组变量,表示线程池的状态:
//
private static final int COUNT_BITS = Integer.SIZE - 3;
// 由28个1二进制组成的数字
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
- 当线程池被创建后,线程池处于 RUNNING 状态;
- 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
- 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
- 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
3、任务缓存之阻塞队列
如果新请求的到达速率超过了线程池的处理速率,则新到达的请求会暂存在线程池管理的Runnable等待队列workQueue中,工作队列为BlockingQueue 类型的阻塞队列。
阻塞队列的操作可分为如下:
抛异常响应 | 特值响应 | 阻塞响应 | 超时响应 | |
---|---|---|---|---|
插入 | add(o) |
offer(o) |
put(o) |
offer(o,timeout,timeunit) |
移除 | remove(o) |
poll(o) |
take(o) |
poll(timeout,timeunit) |
检查 | element(o) |
peek(o) |
- 抛异常响应:如果该操作不能立即满足,则抛出异常。
- 特值响应:如果该操作不能立即执行,则返回一个特值,如 false或null响应。
- 阻塞响应:如果该操作不能立即执行,则会阻塞等待直到满足执行条件。
- 超时响应:如果该操作不能立即执行,则等待一定时间,在该时间内满足条件则执行,否则返回一个特值相应是否执行成功。
BlockingQueue 的实现类:
- ArrayBlockingQueue :是一个有界队列,内部存储结构是数组。在开始使用时需要指定队列大小,且在使用过程中不能对大小进行修改。
- LinkedBlockingQueue :内部使用链表作为存储结构,可以指定大小作为有界队列,如果没指定大小,则默认为 Integer.MAX_VALUE 大小的“无界”队列。
- PriorityBlockingQueue :是一个无界的并发队列,对队列中的元素按照一定的规则进行排序,在线程池中按照线程的优先级进行排序。
- SynchronousQueue :其实不是一个真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入synchronousQueue 队列中,必须有另一个线程正在等待接受这个元素。如果没有线程等待,并且线程池当前大小小于线程池的最大值,那么ThreadPoolExecutror将创建一个新线程来执行此任务。
- DelayQueue :对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口。
阻塞同步的方式:
阻塞队列实现阻塞同步的方式很简单,使用了ReentranLock
的多条件进行阻塞控制,如ArrayBlockingQueue 源码中:
// 构造函数
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
// 空条件、满条件
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
//插入函数
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 可中断的锁
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
// 解锁
lock.unlock();
}
}
阻塞队列的选择策略:
只有当任务相互独立时,为线程池或工作队列设置界线才是合理的。如果任务之间存在依赖性,那么有界的线程池或队列可能会导致“饥饿”死锁问题。可以选择newCachedThreadPool
。
4、四大线程池详解
Java类库提供了灵活的线程池及一些配置,可以通过Executors
中的静态工厂方法进行创建:
1.newFixedThreadPool
- 简介
newFixedThreadPool将创建一个固定长度的线程池,每当提交一个任务,创建一个线程,直到达到线程池的最大数量,这时线程池的规模不再变化,如果某个线程中途应为Exception 异常结束,线程池会再补一个线程加入线程池。
- 源码分析
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
} public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
可以看到,newFixedThreadPool 的 核心池大小和线程池最大大小一致,就是说,该线程池大小在接受任务时,就逐步创建线程到最大值。
线程的存活时间设置为为0毫秒,说明核心池的线程池不会超时而终止,所以核心池的线程数量一旦创建,除非异常终止,不会因为超时等问题而自动停止。看allowCoreThreadTimeOut(boolean)
源码:
public void allowCoreThreadTimeOut(boolean value) {
// 如果keepAliveTime 为0或小于0,则不能设置核心池自动死亡
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers();
}
}
- 分析
使用了无界的LinkedBlockingQueue 阻塞队列,如果任务请求速度大于线程处理速度,可能会导致阻塞队列中堆积了大量待处理的任务,占用大量内存,导致性能下降或是奔溃。
2. newCachedThreadPool
- 简介
newCachedThreadPool 将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,将回收空闲线程,而当需求增加时,则可以添加新的线程,线程池的规模没有限制。
- 源码分析
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
} public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
核心池大小设置为0,线程池的最大大小设置为“无穷大”,说明该线程池没有处于核心池的线程,即,所有线程池的所有线程都是会超时死亡的。线程空闲存活时间为60秒,意味着如果线程空闲60秒就会被杀死。阻塞队列使用了SynchronousQueue队列 ,提交的任务不会暂存到队列中,而是又改队移交到线程直接执行。
- 分析
它提供比固定大小的线程池更好的排队性能、如果任务请求过于频繁,导致任务提交速度大于线程请求速度,可能会使应用程序创建大量的线程导致性能下降甚至奔溃。所以,如果限制当前任务的数量足以满足资源管理的需求,优先选择有界队列。
3. newSingleThreadExecutor
- 简介
newSingleThreadExecutor 是一个单线程的 Executor,它创建单个工作者线程来执行任务,如果该线程异常结束,将创建一个新的线程来代替它。newSingleThreadExecutor 能确保任务依照队列中的顺序来串行执行(例如,FIFO,LIFO,优先级等)。
- 源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
} public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory){
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
核心池和线程池最大大小皆为1,说明该线程池只能容纳一个线程,0毫秒的存活时间,说明该线程不会自动死亡。使用无边界的LinkedBlockingQueue阻塞队列,无法及时处理的任务可能会无限制的堆积在该阻塞队列中,可能造成内存泄漏。
4. newScheduledThreadPool
- 简介
newScheduledThreadPool 创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似Timmer。
- 源码分析
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
// 使用了 DelayedWorkQueue 阻塞队列
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
可以看到 newScheduledThreadPool 返回了一个 ScheduledExecutorService 对象,和之前三个返回的 ExecutorService 不一样。使用了DelayedWorkQueue作为阻塞队列,定时执行。ScheduledThreadPoolExecutor 方法:
// 延迟执行,只会执行一次
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
// 延期定时执行,重复执行多次
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
Java多线程学习之线程池源码详解的更多相关文章
- Java并发编程中线程池源码分析及使用
当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnb ...
- JAVA多线程学习七-线程池
为什么用线程池 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 例如: 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3 如果T1+T3> ...
- Java学习笔记 线程池使用及详解
有点笨,参考了好几篇大佬们写的文章才整理出来的笔记.... 字面意思上解释,线程池就是装有线程的池,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程 ...
- Java多线程学习之wait、notify/notifyAll 详解
1.wait().notify/notifyAll() 方法是Object的本地final方法,无法被重写. 2.wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关 ...
- java多线程——线程池源码分析(一)
本文首发于cdream的个人博客,点击获得更好的阅读体验! 欢迎转载,转载请注明出处. 通常应用多线程技术时,我们并不会直接创建一个线程,因为系统启动一个新线程的成本是比较高的,涉及与操作系统的交互, ...
- java多线程----线程池源码分析
http://www.cnblogs.com/skywang12345/p/3509954.html 线程池示例 在分析线程池之前,先看一个简单的线程池示例. 1 import java.util.c ...
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
随机推荐
- ORALCE PL/SQL学习笔记
ORALCE PL/SQL学习笔记 详情见自己电脑的备份数据资料
- java web 学习笔记 编码问题总结
java web 学习笔记 编码问题总结 1.非form表单中提交的中文参数---------------------------传递给Servlet服务器时,默认以iso-8859-1解码 ...
- WPF Popup全屏 弹出方法。解决只显示75%的问题。
WPF Popup全屏 弹出方法.解决只显示75%的问题. WPF 中 Popup 有一个特点.当Popup的高度超过屏幕的75%的时候,只显示75%的高度. 如下代码: <Window x ...
- mac iterm2安装、sshpass密码记住
1. item2官网下载,自行安装 2. 导入以前生成的Prefiles文件到 /Users/alex/Library/Application Support/iTerm2/DynamicProfi ...
- leetcode 697. Degree of an Array
题目: Given a non-empty array of non-negative integers nums, the degree of this array is defined as th ...
- 反射型 DDoS 攻击的原理和防范措施
随着僵尸网络的兴起,同时由于攻击方法简单.影响较大.难以追查等特点,分布式拒绝服务攻击(DDoS,Distributed Denial of Service)得到快速壮大和日益泛滥. 成千上万主机组成 ...
- win10 uwp 打开文件管理器选择文件
本文:让文件管理器选择文件,不是从文件管理器获得文件. 假如已经获得一些文件,那么如何从文件管理器选择这些文件? 使用方法很简单. 从网上拿图来说 打开文件夹自动选择所有文件 首先需要获得文件夹,因为 ...
- 张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231
GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/DS3231 注意:不包含闹钟设置
- win10 uwp 自定义控件 SplitViewItem
本文主要是因为汉堡菜单里面列出的菜单很多重复的图标和文字,我把它作为控件,因为是随便写,可能存在错误,如果发现了,请和我说或关掉浏览器,请不要发不良言论. 我们使用汉堡菜单,经常需要一个 需要一个图标 ...
- Gradle sync failed 异常
今天开发过程中出现如下异常 Gradle sync failed: Connection timed out: connect. If you are behind an HTTP proxy, pl ...