如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在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自定义线程池的更多相关文章

  1. Java 自定义线程池

    Java 自定义线程池 https://www.cnblogs.com/yaoxiaowen/p/6576898.html public ThreadPoolExecutor(int corePool ...

  2. Java自定义线程池-记录每个线程执行耗时

    ThreadPoolExecutor是可扩展的,其提供了几个可在子类化中改写的方法,如下: protected void beforeExecute(Thread t, Runnable r) { } ...

  3. JAVA并发,线程工厂及自定义线程池

    package com.xt.thinks21_2; import java.util.concurrent.ExecutorService; import java.util.concurrent. ...

  4. java多线程(四)-自定义线程池

    当我们使用 线程池的时候,可以使用 newCachedThreadPool()或者 newFixedThreadPool(int)等方法,其实我们深入到这些方法里面,就可以看到它们的是实现方式是这样的 ...

  5. Android AsyncTask 深度理解、简单封装、任务队列分析、自定义线程池

    前言:由于最近在做SDK的功能,需要设计线程池.看了很多资料不知道从何开始着手,突然发现了AsyncTask有对线程池的封装,so,就拿它开刀,本文将从AsyncTask的基本用法,到简单的封装,再到 ...

  6. Android 自定义线程池的实战

    前言:在上一篇文章中我们讲到了AsyncTask的基本使用.AsyncTask的封装.AsyncTask 的串行/并行线程队列.自定义线程池.线程池的快速创建方式. 对线程池不了解的同学可以先看 An ...

  7. Java Executors(线程池)

    Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定可靠的多线程程序 ...

  8. Java(Android)线程池 总结

        JAVA的Executors源码:(可以看出底层都是通过ThreadPoolExecutor来具体设置的~) public static ExecutorService newCachedTh ...

  9. java自定义连接池

    1.java自定义连接池 1.1连接池的概念: 实际开发中"获取连接"或“释放资源”是非常消耗系统资源的两个过程,为了姐姐此类性能问题,通常情况我们采用连接池技术来贡献连接Conn ...

随机推荐

  1. 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 ...

  2. 使用C#和MSMQ开发消息处理程序

    简介 MSMQ(微软消息队列)是Windows操作系统中消息应用程序的基础,是用于创建分布式.松散连接的消息通讯应用程序的开发工具.消息队列和电子邮件有着很多相似处,他们都包含多个属性,用于保存消息, ...

  3. mac下hbase安装

    出处:https://www.jianshu.com/p/510e1d599123 安装到的路径:/usr/local/Cellar/hbase/1.2.6 linux操作: linux命令 作用 . ...

  4. jquery操作select大全详解

    每一次操作select的时候,总是要出来翻一下资料,不如自己总结一下,以后就翻这里了. 比如<select class="selector"></select&g ...

  5. Mybatis连接Oracle实现增删改查实践

    1. 首先要在项目中增加Mybatis和Oracle的Jar文件 这里我使用的版本为ojdbc7 Mybatis版本为:3.2.4 2. 在Oracle中创建User表 create table T_ ...

  6. jeecg中的原生态组件

    <!-- ztree --><link rel="stylesheet" type="text/css" href="plug-in ...

  7. freeswitch自定义模块的wiki地址

    http://wiki.freeswitch.org/wiki/Authoring_Freeswitch_Modules

  8. Ice框架简介及Vs2013安装Ice 3.7.0步骤及实例

    ICE是什么? ICE是ZEROC官网的开源通信协议产品,它的全称是:The Internet Communications Engine,翻译为中文是互联网通信引擎,是一个面向对象的中间件,支持C+ ...

  9. 峰Spring4学习(3)注入参数的几种类型

    People.java  model类: package com.cy.entity; import java.util.ArrayList; import java.util.HashMap; im ...

  10. SonarQube

    代码质量管理 Sonar 是一个用于代码质量管理的开放平台.通过插件机制,Sonar 可以集成不同的测试工具,代码分析工具,以及持续集成工具.与持续集成工具(例如 Hudson/Jenkins 等)不 ...