jdk自带线程池实例详解
作者:牛奋lch 发布时间:2022-05-14 02:04:56
二、简介
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力,但频繁的创建线程的开销是很大的,那么如何来减少这部分的开销了,那么就要考虑使用线程池了。线程池就是一个线程的容器,每次只执行额定数量的线程,线程池就是用来管理这些额定数量的线程。
三、涉及线程池的类结构图
其中供我们使用的,主要是ThreadPoolExecutor类。
四、如何创建线程池
我们创建线程池一般有以下几种方法:
1、使用Executors工厂类
Executors主要提供了下面几种创建线程池的方法:
下面来看下使用示例:
1)newFixedThreadPool(固定大小的线程池)
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(5);// 创建一个固定大小为5的线程池
for (int i = 0; i < 10; i++) {
pool.submit(new MyThread());
}
pool.shutdown();
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
}
}
测试结果如下:
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-4正在执行。。。
固定大小的线程池:每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程线。
2)newSingleThreadExecutor(单线程的线程池)
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService pool=Executors.newSingleThreadExecutor();//创建一个单线程池
for(int i=0;i<100;i++){
pool.submit(new MyThread());
}
pool.shutdown();
}
}
测试结果如下:
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
单线程的线程池:这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
3)newScheduledThreadPool
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService pool=Executors.newScheduledThreadPool(6);
for(int i=0;i<10000;i++){
pool.submit(new MyThread());
}
pool.schedule(new MyThread(), 1000, TimeUnit.MILLISECONDS);
pool.schedule(new MyThread(), 1000, TimeUnit.MILLISECONDS);
pool.shutdown();
}
}
测试结果如下:
pool-1-thread-1正在执行。。。
pool-1-thread-6正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-6正在执行。。。
pool-1-thread-1正在执行。。。
…………此处会延时1S…………
pool-1-thread-4正在执行。。。
pool-1-thread-1正在执行。。。
测试结果的最后两个线程都是在延时1S之后,才开始执行的。此线程池支持定时以及周期性执行任务的需求
4)newCachedThreadPool(可缓存的线程池)
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService pool=Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
pool.submit(new MyThread());
}
pool.shutdown();
}
}
测试结果如下:
pool-1-thread-5正在执行。。。
pool-1-thread-7正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-16正在执行。。。
pool-1-thread-17正在执行。。。
pool-1-thread-16正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-7正在执行。。。
pool-1-thread-16正在执行。。。
pool-1-thread-18正在执行。。。
pool-1-thread-10正在执行。。。
可缓存的线程池:如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
官方建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()( * 线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程),这几种线程池均为大多数使用场景预定义了默认配置。
2、继承ThreadPoolExecutor类,并复写父类的构造方法。
在介绍这种方式之前,我们来分析下前面几个创建线程池的底层代码是怎样的?
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
}
从Executors工厂类的底层代码可以看出,工厂类提供的创建线程池的方法,其实都是通过构造ThreadPoolExecutor来实现的。ThreadPoolExecutor构造方法代码如下:
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;
}
那么接下来,我们就来谈谈这个ThreadPoolExecutor构造方法。在这个构造方法中,主要有以下几个参数:
corePoolSize--池中所保存的线程数,包括空闲线程。
maximumPoolSize--池中允许的最大线程数。
keepAliveTime--当线程数大于corePoolSize时,此为终止空闲线程等待新任务的最长时间。
Unit--keepAliveTime 参数的时间单位。
workQueue--执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。
threadFactory--执行程序创建新线程时使用的工厂。
Handler--由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
接下来,咋们来说下这几个参数之间的关系。当线程池刚创建的时候,线程池里面是没有任何线程的(注意,并不是线程池一创建,里面就创建了一定数量的线程),当调用execute()方法添加一个任务时,线程池会做如下的判断:
1)如果当前正在运行的线程数量小于corePoolSize,那么立刻创建一个新的线程,执行这个任务。
2)如果当前正在运行的线程数量大于或等于corePoolSize,那么这个任务将会放入队列中。
3)如果线程池的队列已经满了,但是正在运行的线程数量小于maximumPoolSize,那么还是会创建新的线程,执行这个任务。
4)如果队列已经满了,且当前正在运行的线程数量大于或等于maximumPoolSize,那么线程池会根据拒绝执行策略来处理当前的任务。
5)当一个任务执行完后,线程会从队列中取下一个任务来执行,如果队列中没有需要执行的任务,那么这个线程就会处于空闲状态,如果超过了keepAliveTime存活时间,则这个线程会被线程池回收(注:回收线程是有条件的,如果当前运行的线程数量大于corePoolSize的话,这个线程就会被销毁,如果不大于corePoolSize,是不会销毁这个线程的,线程的数量必须保持在corePoolSize数量内).为什么不是线程一空闲就回收,而是需要等到超过keepAliveTime才进行线程的回收了,原因很简单:因为线程的创建和销毁消耗很大,更不能频繁的进行创建和销毁,当超过keepAliveTime后,发现确实用不到这个线程了,才会进行销毁。这其中unit表示keepAliveTime的时间单位,unit的定义如下:
public enum TimeUnit {
NANOSECONDS {
// keepAliveTime以纳秒为单位
},
MICROSECONDS {
// keepAliveTime以微秒为单位
},
MILLISECONDS {
// keepAliveTime以毫秒为单位
},
SECONDS {
// keepAliveTime以秒为单位
},
MINUTES {
// keepAliveTime以分钟为单位
},
HOURS {
// keepAliveTime以小时为单位
},
DAYS {
// keepAliveTime以天为单位
};
下面从源码来分析一下,对于上面的几种情况,主要涉及到的源码有以下几块:
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
其实,这段代码很简单,主要描述的就是,如果当前的线程池小于corePoolSize的时候,是直接新建一个线程来处理任务。
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < maximumPoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
上面这段代码描述的是,如果当前线程池的数量小于maximumPoolSize的时候,也会创建一个线程,来执行任务
五、线程池的队列
线程池的队列,总的来说有3种:
直接提交:工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求 * maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许 * 线程具有增长的可能性。
* 队列:使用 * 队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用 * 队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许 * 线程具有增长的可能性。
有界队列:当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
下面就来说下线程池的队列,类结构图如下:
1)SynchronousQueue
该队列对应的就是上面所说的直接提交,首先SynchronousQueue是 * 的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。
2)LinkedBlockingQueue
该队列对应的就是上面的 * 队列。
3)ArrayBlockingQueue
该队列对应的就是上面的有界队列。ArrayBlockingQueue有以下3中构造方法:
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = (E[]) new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
if (capacity < c.size())
throw new IllegalArgumentException();
for (Iterator<? extends E> it = c.iterator(); it.hasNext();)
add(it.next());
}
下面我们重点来说下这个fair,fair表示队列访问线程的竞争策略,当为true的时候,任务插入队列遵从FIFO的规则,如果为false,则可以“插队”。举个例子,假如现在有很多任务在排队,这个时候正好一个线程执行完了任务,同时又新来了一个任务,如果为false的话,这个任务不用在队列中排队,可以直接插队,然后执行。如下图所示:
六、线程池的拒绝执行策略
当线程的数量达到最大值时,这个时候,任务还在不断的来,这个时候,就只好拒绝接受任务了。
ThreadPoolExecutor 允许自定义当添加任务失败后的执行策略。你可以调用线程池的 setRejectedExecutionHandler() 方法,用自定义的RejectedExecutionHandler 对象替换现有的策略,ThreadPoolExecutor提供的默认的处理策略是直接丢弃,同时抛异常信息,ThreadPoolExecutor 提供 4 个现有的策略,分别是:
ThreadPoolExecutor.AbortPolicy:表示拒绝任务并抛出异常,源码如下:
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an <tt>AbortPolicy</tt>.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always.
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException(); //抛异常
}
}
ThreadPoolExecutor.DiscardPolicy:表示拒绝任务但不做任何动作,源码如下:
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a <tt>DiscardPolicy</tt>.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
} // 直接拒绝,但不做任何操作
}
ThreadPoolExecutor.CallerRunsPolicy:表示拒绝任务,并在调用者的线程中直接执行该任务,源码如下:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a <tt>CallerRunsPolicy</tt>.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run(); // 直接执行任务
}
}
}
ThreadPoolExecutor.DiscardOldestPolicy:表示先丢弃任务队列中的第一个任务,然后把这个任务加进队列。源码如下:
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a <tt>DiscardOldestPolicy</tt> for the given executor.
*/
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 丢弃队列中的第一个任务
e.execute(r); // 执行新任务
}
}
}
当任务源源不断到来的时候,会从Queue中poll一个任务出来,然后执行新的任务
总结
以上所述是小编给大家介绍的jdk自带线程池详解网站的支持!
来源:http://blog.csdn.net/liuchuanhong1/article/details/52042182


猜你喜欢
- 本文实例讲述了C#实现动态生成静态页面的类。分享给大家供大家参考,具体如下:动态生成静态页面有许多好处,比如生成html网页有利于被搜索引擎
- 1、Hello, KotlinBugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验
- 我先说说这两种的方式的不同之处吧 第一种: 在调动成功之后 不会让你指纹解锁 而是调转到当初你设置指纹解锁时的 手势解锁页面 第二种: 在调
- 一、题目描述题目:有五个学生,每个学生有 3 门课的成绩,从键盘输入以上数据(包括学生号,姓名,三门课成绩),把这些数据存放在磁盘文件 &q
- 我们在使用APP的过程中,软件会偶尔提示我们进行版本更新,我们点击确认更新后,会在通知栏显示下载更新进度(已知长度的进度条)以及安装情况(不
- 目录@CachePut设置的key值无法与@CacheValue的值匹配缓存注解key的基本数据类型要求必须要统一Spring-Cache
- 前言在java里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝。浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对
- 本文实例讲述了Android编程实现自定义系统菜单背景的方法。分享给大家供大家参考,具体如下:不多说,上图,见代码。package lab.
- using System; using System.IO; using System.Data; using System.Text; u
- 常用命令:打包:mvn package编译:mvn compile清空:mvn clean(清除编译后目录,默认是target目录)运行测试
- 这个CardStackViewpager的灵感来自Github上面的 FlippableStackView开源项目,而我想实现的效果方向上恰
- 1.新建一个数组,把原来数组的内容搬到新数组中。这种方法实现的思路是:先新建一个数组(前提条件是长度得比原来的长),然后把原来数组的内容搬到
- 话不多说,请看下面//C# 代码int year = DateTime.Now.Year;int month = DateTime.Now.
- 什么是JPA一种规范,并非ORM框架,也就是ORM上统一的规范spring-boot-starter-data-jpa 
- 重写 equals()方法 和 hashCode()方法最近看了学习了集合的简单的知识,碰到了讲解 Set 的部分,感觉很好奇,这里对于 S
- java多态性多态分两种:(1) 编译时多态(设计时多态):方法重载。(2) 运行时多态:J
- 本文实例讲述了如何计算(或者说,估算)一个Java对象占用的内存数量的方法。分享给大家供大家参考。具体分析如下:通常,我们谈论的堆内存使用的
- 本文实例为大家分享了Java NIO实现聊天功能的具体代码,供大家参考,具体内容如下server code : package c
- 本文实例讲述了Java面向接口编程之命令模式。分享给大家供大家参考,具体如下:一 点睛某个方法需要完成某个行为,但这个行为的具体实现无法确定
- 本文实例为大家分享了一个基于JAVA的知乎爬虫,抓取知乎用户基本信息,基于HttpClient 4.5,供大家参考,具体内容如下详细内容:抓