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 ...
随机推荐
- nginx unit nodejs 模块试用
unit 对于nodejs 的支持是在10.25 发布的,基本能用,但是依然有好多问题,当前在测试的时候就发现,请求之后会block , 相关的issue 已经有人反馈了,最好使用源码编译,方便测 ...
- adnanh webhook 框架 hook 定义
Hook hook 是一个SON对象.钩子对象必须包含id和execute-command属性.所有其他属性都被视为可选. 属性 id - 指定hook的ID.方式格式(http://server:p ...
- TensorFlow笔记-02-Windows下搭建TensorFlow环境(win版非虚拟机)
TensorFlow笔记-02-Windows下搭建TensorFlow环境(win版非虚拟机) 本篇介绍的是在windows系统下,使用 Anaconda+PyCharm,不使用虚拟机,也不使用 L ...
- 为什么有时候 php 没有写闭合标签结束符?
找了一些资料,大家对PHP闭合标签的总结如下: 好处:如果这个是一个被别人包含的程序,没有这个结束符,可以减少很多很多问题,比如说:header, setcookie, session_start这些 ...
- 【转】Android开源项目(非组件)
原文网址:http://blog.csdn.net/feizhixuan46789/article/details/9252887 学习开发一个有效的途径就是借鉴成熟的案例作为学习的对象,下面为大家推 ...
- kvm 基本运维命令
Kvm基本命令 一.查询命令 1.列出所有的虚拟机 virsh list –all 2.显示虚拟机信息 virsh dominfo kvm-1 3.显示虚拟机内存和cpu的使用情况 yum insta ...
- json格式字符串处理
public class InternalClass { public int MID; public string Name; ...
- make dep
在配置好内核后就是编译内核了,在编译之前首先应该执行make dep命令建立好依赖关系,该命令将会修改linux中每个子目录下的.depend文件,该文件包含了该目录下每个目标文件所需要的头文件(绝对 ...
- Hadoop专业解决方案-第1章 大数据和Hadoop生态圈
一.前言: 非常感谢Hadoop专业解决方案群:313702010,兄弟们的大力支持,在此说一声辛苦了,经过两周的努力,已经有啦初步的成果,目前第1章 大数据和Hadoop生态圈小组已经翻译完成,在此 ...
- bzoj3503 和谐矩阵
Description 我们称一个由0和1组成的矩阵是和谐的,当且仅当每个元素都有偶数个相邻的1.一个元素相邻的元素包括它本身,及他上下左右的4个元素(如果存在).给定矩阵的行数和列数,请计算并输出一 ...