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 ...
随机推荐
- Json.NET Updates: Merge, Dependency Injection, F# and JSONPath Support
Json.NET 6.0 received 4 releases this year, the latest last week. Over these releases, several new f ...
- 使用C#和MSMQ开发消息处理程序
简介 MSMQ(微软消息队列)是Windows操作系统中消息应用程序的基础,是用于创建分布式.松散连接的消息通讯应用程序的开发工具.消息队列和电子邮件有着很多相似处,他们都包含多个属性,用于保存消息, ...
- mac下hbase安装
出处:https://www.jianshu.com/p/510e1d599123 安装到的路径:/usr/local/Cellar/hbase/1.2.6 linux操作: linux命令 作用 . ...
- jquery操作select大全详解
每一次操作select的时候,总是要出来翻一下资料,不如自己总结一下,以后就翻这里了. 比如<select class="selector"></select&g ...
- Mybatis连接Oracle实现增删改查实践
1. 首先要在项目中增加Mybatis和Oracle的Jar文件 这里我使用的版本为ojdbc7 Mybatis版本为:3.2.4 2. 在Oracle中创建User表 create table T_ ...
- jeecg中的原生态组件
<!-- ztree --><link rel="stylesheet" type="text/css" href="plug-in ...
- freeswitch自定义模块的wiki地址
http://wiki.freeswitch.org/wiki/Authoring_Freeswitch_Modules
- Ice框架简介及Vs2013安装Ice 3.7.0步骤及实例
ICE是什么? ICE是ZEROC官网的开源通信协议产品,它的全称是:The Internet Communications Engine,翻译为中文是互联网通信引擎,是一个面向对象的中间件,支持C+ ...
- 峰Spring4学习(3)注入参数的几种类型
People.java model类: package com.cy.entity; import java.util.ArrayList; import java.util.HashMap; im ...
- SonarQube
代码质量管理 Sonar 是一个用于代码质量管理的开放平台.通过插件机制,Sonar 可以集成不同的测试工具,代码分析工具,以及持续集成工具.与持续集成工具(例如 Hudson/Jenkins 等)不 ...