java自定义线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。首先我们从最核心的ThreadPoolExecutor类中的方法讲起。
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,默认有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池执行的流程:
当任务提交给ThreadPoolExecutor 线程池中,先检查核心线程数是否已经全部使用,如果没有交由核心线程去执行任务,如果核心线程数已经全部占用,则将任务添加到队列里面,如果队列已经占满,比较当前线程池的中线程的数量是不是与超过maximumPoolSize,如果没有查过则创建线程去执行,也就是说线程池最多可以接受多少任务呢?就是maximumPoolSize+队列的大小。当线程池中的线程的数量大于corePoolSize数量有空闲线程则执行回收,回收时间是keepAliveTime,单位是unit,都是初始化的时候设置的。
下面通过代码来说明:
定义一个实现了Runnable接口的类,当作任务类;
public class MyTask implements Runnable {
private int taskId;
private String taskName;
public int getTaskId() {
return taskId;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public MyTask(int taskId, String taskName) {
this.taskId = taskId;
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("taskId:" + taskId + ",taskName:" + taskName);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
定义如下的线程池:
ThreadPoolExecutor pool = new ThreadPoolExecutor(// 自定义一个线程池
1, // coreSize
2, // maxSize
60, // 60s
TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列,容量是3个
, Executors.defaultThreadFactory()
, new ThreadPoolExecutor.AbortPolicy()
);
该线程池最多可以放2+3个任务,现在我们放6个任务进去,看看执行的效果:
pool.execute(new MyTask(1, "任务1"));
System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());
pool.execute(new MyTask(2, "任务2"));
System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());
pool.execute(new MyTask(3, "任务3"));
System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());
pool.execute(new MyTask(4, "任务4"));
System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());
pool.execute(new MyTask(5, "任务5"));
System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());
pool.execute(new MyTask(6, "任务6"));
System.out.println("活跃的线程数:"+pool.getActiveCount() + ",核心线程数:" + pool.getCorePoolSize() + ",线程池大小:" + pool.getPoolSize() + ",队列的大小" + pool.getQueue().size());
pool.shutdown();
我们的执行结果:

我们可以看到,首先抛出了异常,大致意思是拒绝了一个线程加入到线程池,因为我线程池最大允许5个线程的加入,当线程池满了执行的拒绝策略是DiscardPolicy直接拒绝线程的加入,并抛出异常。
接下来,我们看每次添加一个线程打印的活跃的线程数等相关消息。
当任务1加入到线程池中:
活跃的线程数:1,核心线程数:1,线程池大小:1,队列的大小0,也就是说,任务1加入核心未被占满,开启一个核心线程去执行。此时线程的大小也为1.
当任务2加入到线程池中时:
活跃的线程数:1,核心线程数:1,线程池大小:1,队列的大小1 。也就是说,此时核心已经占满了,队列没有满,则往队列里面增加任务。此时线程的大小仍然也为1.因为就一个核心线程在执行任务。
当任务3加入到线程池中时:同任务2.
当任务4加入到线程池中时:同任务2.此时队列已经满。
当任务5加入到线程池中时:
活跃的线程数:2,核心线程数:1,线程池大小:2,队列的大小3,活跃的线程变为2,也就是maximumPoolSize数量,因为任务4加入到线程池时,线程池的队列已经满了,此时会检查活跃的线程是不是大于maximumPoolSize,如果不大于则创建线程去执行任务,到底执行新加入还是队列里面最老加入的。此时通过下面的执行结果来判断。

我们看到任务1和任务5最先执行,任务1不用讲自然会在最先执行的里面,任务5在最先执行的任务里面,说明,当线程队列满了,如果开起了新线程,则会去执行新加入的任务,不是从队列里面去老的任务。从后面执行来看,当之前的任务直线完成了,线程池会从队列里面获取任务去执行。这就是一个线程池的大致执行流程。
当然个人觉得那个原生的拒绝策略都不太实用,比如互联网任务,我们定义一个线程池,当线程池满了,我们要合理的处理后续的任务,比如记录下来下次再去执行,或者告知责任人那些任务没有处理等等,个人任务这个应该自己定义,当线程满了,我们可以自由控制。下面定义一个拒绝策略。
public class MyRejected implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
MyTask task = (MyTask) r;
System.out.println("报警信息:"+task.getTaskName()+" 被线程池拒绝,没有被执行");
//可以往消息队列中间件里面放 可以发Email等等
}
}
如上,我们实现RejectedExecutionHandler 接口。就可以自定义一个拒绝策略很简单。
我们需要线程池的定义,使用自己的拒绝策略。
ThreadPoolExecutor pool = new ThreadPoolExecutor(// 自定义一个线程池
1, // coreSize
2, // maxSize
60, // 60s
TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列,容量是3个
, Executors.defaultThreadFactory()
, new MyRejected()
);
其他的代码不用修改,执行结果如下:

现在就执行了自己自定义拒绝策略。
以上只是讲解的自定义的线程池,当然java本身已经内置了一些线程,比如:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
这里就不详细讲解了,内部实现都是ThreadPoolExecutor ,个人倾向自定义的线程池,这样比较灵活。
java自定义线程池的更多相关文章
- Java  自定义线程池
		Java 自定义线程池 https://www.cnblogs.com/yaoxiaowen/p/6576898.html public ThreadPoolExecutor(int corePool ... 
- Java自定义线程池-记录每个线程执行耗时
		ThreadPoolExecutor是可扩展的,其提供了几个可在子类化中改写的方法,如下: protected void beforeExecute(Thread t, Runnable r) { } ... 
- JAVA并发,线程工厂及自定义线程池
		package com.xt.thinks21_2; import java.util.concurrent.ExecutorService; import java.util.concurrent. ... 
- java多线程(四)-自定义线程池
		当我们使用 线程池的时候,可以使用 newCachedThreadPool()或者 newFixedThreadPool(int)等方法,其实我们深入到这些方法里面,就可以看到它们的是实现方式是这样的 ... 
- Android AsyncTask 深度理解、简单封装、任务队列分析、自定义线程池
		前言:由于最近在做SDK的功能,需要设计线程池.看了很多资料不知道从何开始着手,突然发现了AsyncTask有对线程池的封装,so,就拿它开刀,本文将从AsyncTask的基本用法,到简单的封装,再到 ... 
- Android 自定义线程池的实战
		前言:在上一篇文章中我们讲到了AsyncTask的基本使用.AsyncTask的封装.AsyncTask 的串行/并行线程队列.自定义线程池.线程池的快速创建方式. 对线程池不了解的同学可以先看 An ... 
- Java Executors(线程池)
		Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定可靠的多线程程序 ... 
- Java(Android)线程池 总结
		JAVA的Executors源码:(可以看出底层都是通过ThreadPoolExecutor来具体设置的~) public static ExecutorService newCachedTh ... 
- java自定义连接池
		1.java自定义连接池 1.1连接池的概念: 实际开发中"获取连接"或“释放资源”是非常消耗系统资源的两个过程,为了姐姐此类性能问题,通常情况我们采用连接池技术来贡献连接Conn ... 
随机推荐
- Cocos2D-X2.2.3学习笔记13(延时动作)
			版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/q269399361/article/details/28265477 还记得我们上一节讲的瞬时动作吗 ... 
- docker 知识点
			docker 教程:http://www.runoob.com/docker/docker-tutorial.html docker 仓库地址:https://store.docker.com/ do ... 
- POJ1015陪审团(Jury Compromise)——dp+路径记录
			题目:http://poj.org/problem?id=1015 差值是有后效性的,所以“转化为可行性”,开一维记录“能否达到这个差值”. 当然可以开两维分别记录 a 和 b,但 “值只是0或1” ... 
- mySQL 教程 第3章 数据类型和数据完整性
			准备数据库 创建练习数据库,以下实验都是在这个数据库中完成. 练习1:比较各种数值型 create table tmp1 ( id INT, name VARCHAR(25), deptId deci ... 
- linux非root用户执行开机启动程序
			问题 开机启动其他用户的程序或者说非root用户执行开机启动 编写开机启动脚本 编写开机启动脚本apple_tree,放到/etc/init.d,系统启动时会自动执行. 例如,/etc/init.d/ ... 
- .Net和SqlServer的事务处理实例
			1,SqlServer存储过程的事务处理一种比较通用的出错处理的模式大概如下:Create procdure prInsertProducts( @intProductId int, @chvProd ... 
- bzoj2262: 平行宇宙与虫洞
			Description 量子力学指出,宇宙并非只有一种形态. 根据量子理论,一件事件发生之后可以产生不同的后果,而所有可能的后果都会形成自己的宇宙. 我们可以把一个宇宙看成一个时间轴,虫洞可以看成不同 ... 
- ASP.NET Web Pages:发布网站
			ylbtech-.Net-ASP.NET Web Pages:发布网站 1.返回顶部 1. ASP.NET Web Pages - 发布网站 学习如何在不使用 WebMatrix 的情况下发布 Web ... 
- linux 添加secondary ip
			linux下ip地址除了primary外,还有两种:1. ip alias(子接口)2. secondary ip(辅助ip) 都可在一块物理网卡上添加,alias由ifconfig添加,ifconf ... 
- 第10课 C++异常简介
			1. try-catch语句 (1)try语句处理正常代码逻辑 (2)catch语句处理异常情况 (3)try语句中的异常由对应的catch语句处理 (4)C++通过throw语句抛出异常信息 2. ... 
