博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java线程池ThreadPoolExecutor与四种常见线程池
阅读量:3898 次
发布时间:2019-05-23

本文共 20404 字,大约阅读时间需要 68 分钟。

java线程池总结

一 、ThreadPoolExecutor线程池

本文链接:https://blog.csdn.net/qq_41135605/article/details/114453105

​ Java中的线程池顶层接口是Executor接口,ThreadPoolExecutor是这个接口的实现类。

​ 在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

​ 下面我们就对ThreadPoolExecutor的使用方法进行一个详细的概述。

​ 首先看下ThreadPoolExecutor的构造函数。

1.1 线程池的4个构造方法。

// 五个参数的构造函数public ThreadPoolExecutor(int corePoolSize,//核心线程数                          int maximumPoolSize,//最大线程数                          long keepAliveTime,//非核心线程最大存活时长                          TimeUnit unit,//时长单位                          BlockingQueue
workQueue)//阻塞队列,存放着等待执行的线程任务// 六个参数的构造函数-1public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, ThreadFactory threadFactory)//创建线程的工厂// 六个参数的构造函数-2public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, RejectedExecutionHandler handler)//拒绝策略,当线程池无法再接受任务时调用// 七个参数的构造函数public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }

1.1.1 **参数解释:

**最多的构造函数有七个入参,最少的5个入参。这里为5个必传,和2个非必传(有默认值)。

  1. 5个必填
  • corePoolSize:指定了线程池中的核心线程数,即不会因线程空闲而被销毁的线程。它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去。

    核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干(铁饭碗),而非核心线程如果长时间的闲置,就会被销毁(临时工)。

  • maximumPoolSize: 指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量。

    该值等于核心线程数量 + 非核心线程数量。

  • keepAliveTime:非核心线程最大存活时长。

    非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true),则会也作用于核心线程。

    非核心线程:当线程池中空闲线程数量超过corePoolSize时,多余的线程就叫非核心线程。

  • unitkeepAliveTime的单位。

    TimeUnit是一个枚举类型 ,包括以下属性:

    NANOSECONDS : 1微毫秒 = 1微秒 / 1000 MICROSECONDS : 1微秒 = 1毫秒 / 1000 MILLISECONDS : 1毫秒 = 1秒 /1000 SECONDS : 秒 MINUTES : 分 HOURS : 小时 DAYS : 天

  • workQueue:阻塞队列,存放着等待执行的Runnable任务对象。它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列、延迟队列几种。

    详细说说几种常见的workQueue:

    • SynchronousQueue:同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
    • LinkedBlockingQueue:链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE,也可以指定大小。
    • ArrayBlockingQueue:数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
    • PriorityBlockingQueue:基于优先级的无界阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),内部控制线程同步的锁采用的是非公平锁。
    • DelayQueue:延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素

    当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

    将在下一节中重点介绍各种阻塞队列。

  1. 2个非必传
  • threadFactory:创建线程的工厂,一般用默认即可。可以自定义线程的名字,设置一些参数等等,如果不想自定义,可以使用默认的Executors.defaultThreadFactory()创建工厂。
  • handle:拒绝策略,当线程池数量大于maximumPoolSize,则执行拒绝策略
  • 再说说几种拒绝策略:
    • ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常。
    • ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常.
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)。
    • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

1.1.2 ThreadPoolExecutor的策略

线程池本身有一个调度线程,这个线程就是用于管理布控整个线程池里的各种任务和事务,例如创建线程、销毁线程、任务队列管理、线程队列管理等等。

故线程池也有自己的状态。ThreadPoolExecutor类中定义了一个volatile int变量runState来表示线程池的状态 ,分别为RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。

  • 线程池创建后处于RUNNING状态。

  • 调用shutdown()方法后处于SHUTDOWN状态,线程池不能接受新的任务,清除一些空闲worker,会等待阻塞队列的任务完成。

  • 调用shutdownNow()方法后处于STOP状态,线程池不能接受新的任务,中断所有线程,阻塞队列中没有被执行的任务全部丢弃。此时,poolsize=0,阻塞队列的size也为0。

  • 当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。接着会执行terminated()函数。

    ThreadPoolExecutor中有一个控制状态的属性叫ctl,它是一个AtomicInteger类型的变量。

  • 线程池处在TIDYING状态时,执行完terminated()方法之后,就会由 TIDYING -> TERMINATED, 线程池被设置为TERMINATED状态。

1.2 参数实例解释

1.2.1 阻塞队列BlockingQueue workQueue

上面我们已经介绍过了,它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列、延迟队列;

  • BlockingQueue的操作方法

    阻塞队列提供了四组不同的方法用于插入、移除、检查元素:

    方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
    插入方法 add(e) offer(e) put(e) offer(e,time,unit)
    移除方法 remove() poll() take() poll(time,unit)
    检查方法 element() peek() - -
    • 抛出异常:如果试图的操作无法立即执行,抛异常。当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
    • 返回特殊值:如果试图的操作无法立即执行,返回一个特殊值,通常是true / false
    • 一直阻塞:如果试图的操作无法立即执行,则一直阻塞或者响应中断。
    • 超时退出:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功,通常是true / false
  • 注意之处

    • 不能往阻塞队列中插入null,会抛出空指针异常。
    • 可以访问阻塞队列中的任意元素,调用remove(o)可以将队列之中的特定对象移除,但并不高效,尽量避免使用。

SynchronousQueue

1、直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,m每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。

public class ThreadPool {
private static ExecutorService pool; public static void main( String[] args ) {
//maximumPoolSize设置为2 ,小于线程数3,拒绝策略为AbortPolic策略,直接抛出异常 pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue
(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); for(int i=0;i<3;i++) {
pool.execute(new ThreadTask()); } }}public class ThreadTask implements Runnable{
public ThreadTask() {
} @Override public void run() {
System.out.println(Thread.currentThread().getName()); }}

输出结果为:

pool-1-thread-1pool-1-thread-2Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.hhxx.test.ThreadTask@55f96302 rejected from java.util.concurrent.ThreadPoolExecutor@3d4eac69[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 2]    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)    at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)    at com.hhxx.test.ThreadPool.main(ThreadPool.java:17)

​ 可以看到,当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。

​ 使用SynchronousQueue队列,提交的任务不会被保存,总是会**马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来**,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

ArrayBlockingQueue

2、有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue
(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

​ 使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

LinkedBlockingQueue

3、无界的任务队列:无界任务队列可以使用LinkedBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

​ 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题

PriorityBlockingQueue

4**、优先任务队列:**优先任务队列通过PriorityBlockingQueue实现.

基于优先级的无界阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),内部控制线程同步的锁采用的是非公平锁。

网上大部分博客上PriorityBlockingQueue为公平锁,其实是不对的,查阅源码:

public PriorityBlockingQueue(int initialCapacity, Comparator
comparator) {
this.lock = new ReentrantLock(); //默认构造方法-非公平锁` ...//其余代码略` }

下面我们通过一个例子演示下:

public class ThreadPool {
private static ExecutorService pool; public static void main( String[] args ) {
//优先任务队列 pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue
(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); for(int i=0;i<10;i++) {
pool.execute(new ThreadTask(i)); } }}public class ThreadTask implements Runnable,Comparable
{
private int priority; //getter public int getPriority() {
return priority; } //setter public void setPriority(int priority) {
this.priority = priority; } //noArgsConstructor public ThreadTask() {
} //oneArgsConstructor public ThreadTask(int priority) {
this.priority = priority; } //当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高 @Override public int compareTo(ThreadTask o) {
return this.priority>o.priority?-1:1; } @Override public void run() {
try {
//让线程阻塞,使后续任务进入缓存队列 Thread.sleep(1000); System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName()); } catch (InterruptedException e) {
// TODO Auto-generated catch block e.printStackTrace(); } } }

我们来看下执行的结果情况:

priority:0,ThreadName:pool-1-thread-1priority:9,ThreadName:pool-1-thread-1priority:8,ThreadName:pool-1-thread-1priority:7,ThreadName:pool-1-thread-1priority:6,ThreadName:pool-1-thread-1priority:5,ThreadName:pool-1-thread-1priority:4,ThreadName:pool-1-thread-1priority:3,ThreadName:pool-1-thread-1priority:2,ThreadName:pool-1-thread-1priority:1,ThreadName:pool-1-thread-1

​ 大家可以看到除了第一个任务直接创建线程执行外其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行且线程池的线程数一直为corePoolSize,也就是只有一个

​ 通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

DelayQueue

​ 延迟队列DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

​ 该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。注入其中的元素必须实现 java.util.concurrent.Delayed接口。

1.2.2 拒绝策略handler

​ 一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutorRejectedExecutionHandler参数即合理的拒绝策略,来处理线程池**“超载”**的情况。ThreadPoolExecutor自带的拒绝策略如下:

1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;

2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;

3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;

4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

当然你也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略,我们看下示例代码:

public class ThreadPool {
private static ExecutorService pool; public static void main( String[] args ) {
//自定义拒绝策略 pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue
(5), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略"); } }); for(int i=0;i<10;i++) {
pool.execute(new ThreadTask()); } }}public class ThreadTask implements Runnable{
@Override public void run() {
try {
//让线程阻塞,使后续任务进入缓存队列 Thread.sleep(1000); System.out.println("ThreadName:"+Thread.currentThread().getName()); } catch (InterruptedException e) {
// TODO Auto-generated catch block e.printStackTrace(); } }}

输出结果:

com.hhxx.test.ThreadTask@33909752执行了拒绝策略com.hhxx.test.ThreadTask@55f96302执行了拒绝策略com.hhxx.test.ThreadTask@3d4eac69执行了拒绝策略ThreadName:pool-1-thread-2ThreadName:pool-1-thread-1ThreadName:pool-1-thread-1ThreadName:pool-1-thread-2ThreadName:pool-1-thread-1ThreadName:pool-1-thread-2ThreadName:pool-1-thread-1

可以看到由于任务加了休眠阻塞,执行需要花费一定时间,导致会有一定的任务被丢弃,从而执行自定义的拒绝策略;

1.2.3 ThreadFactory自定义线程创建

线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等,下面代码我们通过ThreadFactory对线程池中创建的线程进行记录与命名。

public class ThreadPool {
private static ExecutorService pool; public static void main( String[] args ) {
//自定义线程工厂 pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue
(5), new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
System.out.println("线程"+r.hashCode()+"创建"); //线程命名 Thread th = new Thread(r,"threadPool"+r.hashCode()); return th; } }, new ThreadPoolExecutor.CallerRunsPolicy()); for(int i=0;i<10;i++) {
pool.execute(new ThreadTask()); } }}public class ThreadTask implements Runnable{
@Override public void run() {
//输出执行线程的名称 System.out.println("ThreadName:"+Thread.currentThread().getName()); }}

我们看下输出结果:

线程118352462创建线程1550089733创建线程865113938创建ThreadName:threadPool1550089733ThreadName:threadPool118352462线程1442407170创建ThreadName:threadPool1550089733ThreadName:threadPool1550089733ThreadName:threadPool1550089733ThreadName:threadPool865113938ThreadName:threadPool865113938ThreadName:threadPool118352462ThreadName:threadPool1550089733ThreadName:threadPool1442407170

可以看到线程池中,每个线程的创建我们都进行了记录输出与命名。

1.2.4 ThreadPoolExecutor扩展

ThreadPoolExecutor扩展主要是围绕beforeExecute()afterExecute()terminated()三个接口实现的,

1、beforeExecute:线程池中任务运行前执行

2、afterExecute:线程池中任务运行完毕后执行

3、terminated:线程池退出后执行

通过这三个接口我们可以监控每个任务的开始和结束时间,或者其他一些功能。下面我们可以通过代码实现一下:

public class ThreadPool {
private static ExecutorService pool; public static void main( String[] args ) throws InterruptedException {
//实现自定义接口 pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue
(5), new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
System.out.println("线程"+r.hashCode()+"创建"); //线程命名 Thread th = new Thread(r,"threadPool"+r.hashCode()); return th; } }, new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override protected void beforeExecute(Thread t,Runnable r) {
System.out.println("准备执行:"+ ((ThreadTask)r).getTaskName()); } @Override protected void afterExecute(Runnable r,Throwable t) {
System.out.println("执行完毕:"+((ThreadTask)r).getTaskName()); } @Override protected void terminated() {
System.out.println("线程池退出"); } }; for(int i=0;i<10;i++) {
pool.execute(new ThreadTask("Task"+i)); } pool.shutdown(); }}public class ThreadTask implements Runnable{
private String taskName; public String getTaskName() {
return taskName; } public void setTaskName(String taskName) {
this.taskName = taskName; } //constructor public ThreadTask(String name) {
this.setTaskName(name); } @Override public void run() {
//输出执行线程的名称 System.out.println("TaskName"+this.getTaskName()+"---ThreadName:"+Thread.currentThread().getName()); }}

我看下输出结果:

线程118352462创建线程1550089733创建准备执行:Task0准备执行:Task1TaskNameTask0---ThreadName:threadPool118352462线程865113938创建执行完毕:Task0TaskNameTask1---ThreadName:threadPool1550089733执行完毕:Task1准备执行:Task3TaskNameTask3---ThreadName:threadPool1550089733执行完毕:Task3准备执行:Task2准备执行:Task4TaskNameTask4---ThreadName:threadPool1550089733执行完毕:Task4准备执行:Task5TaskNameTask5---ThreadName:threadPool1550089733执行完毕:Task5准备执行:Task6TaskNameTask6---ThreadName:threadPool1550089733执行完毕:Task6准备执行:Task8TaskNameTask8---ThreadName:threadPool1550089733执行完毕:Task8准备执行:Task9TaskNameTask9---ThreadName:threadPool1550089733准备执行:Task7执行完毕:Task9TaskNameTask2---ThreadName:threadPool118352462TaskNameTask7---ThreadName:threadPool865113938执行完毕:Task7执行完毕:Task2线程池退出

可以看到通过对beforeExecute()afterExecute()terminated()的实现,我们对线程池中线程的运行状态进行了监控,在其执行前后输出了相关打印信息。另外使用shutdown方法可以比较安全的关闭线程池, 当线程池调用该方法后,线程池中不再接受后续添加的任务。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出

1.2.5 线程池线程数量

线程池线程数量的设置没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可:

/**             * Ncpu=CPU数量             * Ucpu=目标CPU的使用率,0<=Ucpu<=1             * W/C=任务等待时间与任务计算时间的比率             */            Nthreads = Ncpu*Ucpu*(1+W/C)

以上就是对ThreadPoolExecutor类从构造函数、拒绝策略、自定义线程创建等方面介绍了其详细的使用方法,从而我们可以根据自己的需要,灵活配置和使用线程池创建线程,其中如有不足与不正确的地方还望指出与海涵。

1.3 线程池的处理流程

  1. 如果线程池中线程总数量 < 核心线程数,不管核心线程是否有空闲,就会创建核心线程,以便于线程池中以最快速度达到核心线程数,但前提是线程池中线程总数量 < 核心线程数
  2. 线程池中线程总数量 >= 核心线程数时,新来的的线程任务就会进入到缓存队列中存放,然后空闲的核心线程会来取出并执行线程任务。
  3. 当缓存队列中也满了,说明这个时候系统的并发很高,这是线程池就需要一些帮手了,会创建非核心线程,用于处理新来的线程任务,以便于快速处理业务,创建非核心线程的前提是 线程池最大线程数 < 核心线程数 + 非核心线程数,也就是说核心线程数和非核心线程数之和不能超过最大线程数,可以等于。
  4. 当缓存队列满了,线程池中线程数也达到了设置的最大线程数,这是新来的任务,线程池就会用拒绝策略去拒绝这些任务。
    借鉴一张图,可以很清楚的理解
img

二、四种常见的线程池

Executors类中提供了几个静态方法实现了创建线程池,如果能理解上面的七个参数的意义,那么静态方法创建线程池可以说很好理解。

2.1 newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
());}

可以看到,因为核心线程数为0,非核心线程数为Integer.MAX_VALUE,所以这是一个线程只要空闲60秒就会被回收的线程池,适用于短时间高并发的处理业务,而在峰值过后并不会占用系统资源。

2.2 newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
());}

核心线程数量和总线程数量相等,都是传入的参数nThreads,这是一个池中都是核心线程的线程池,所有线程都不会销毁。执行任务的全是核心线程,当没有空闲的核心线程时,任务会进入到阻塞队列,直到有空闲的核心线程才会去从阻塞队列中取出任务并执行,也导致该线程池基本不会发生使用拒绝策略拒绝任务。还有因为LinkedBlockingQueue阻塞队列的大小默认是Integer.MAX_VALUE,如果使用不当,很可能导致内存溢出

与CachedThreadPool的区别

- 因为` corePoolSize == maximumPoolSize` ,所以`FixedThreadPool`只会创建核心线程。 而`CachedThreadPool`因为`corePoolSize=0`,所以只会创建非核心线程。- `FixedThreadPool`在` getTask() `方法,如果队列里没有任务可取,线程会一直阻塞在` LinkedBlockingQueue.take()` ,线程不会被回收。` CachedThreadPool`会在60s后收回。- 由于线程不会被回收,会一直卡在阻塞,所以**没有任务的情况下, `FixedThreadPool`占用资源更多**。- 都几乎不会触发拒绝策略,但是原理不同。`FixedThreadPool`是因为阻塞队列可以很大(最大为`Integer.MAX_VALUE`),故几乎不会触发拒绝策略;`CachedThreadPool`是因为线程池很大(最大为`Integer.MAX_VALUE`),几乎不会导致线程数量大于最大线程数,故几乎不会触发拒绝策略。

2.3 newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
()));}

有且仅有一个核心线程的线程池( corePoolSize == maximumPoolSize=1),使用了LinkedBlockingQueue(容量很大),所以,不会创建非核心线程。所有任务按照先来先执行的顺序执行。如果这个唯一的线程不空闲,那么新来的任务会存储在任务队列里等待执行。

2.4 newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);}public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, //注意这里使用延迟队列 new DelayedWorkQueue());}

创建一个定长线程池,支持定时及周期性任务执行,这是一个支持延时任务执行的线程池。

具体使用:

ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);// 入参Runnable实例,延时时间,时间单位pool.schedule(() -> System.out.println("执行1"), 5, TimeUnit.SECONDS);pool.schedule(() -> System.out.println("执行2"), 4, TimeUnit.SECONDS);pool.schedule(() -> System.out.println("执行3"), 6, TimeUnit.SECONDS);
//打印结果 执行2 执行1 执行3

————————————————

参考文章:

你可能感兴趣的文章
Linux 搭建 discuz 论坛
查看>>
如何在discuz帖子中插入视频
查看>>
怎么更改织梦网站logo和默认广告
查看>>
织梦系统如何插入优酷视频?
查看>>
Discuz设置特定用户组不启用验证码发帖权限
查看>>
百度云服务器 CentOS 图形界面支持
查看>>
为什么要使用R语言?历数R的优势与缺点
查看>>
[小技巧] Linux 下查询图片的大小
查看>>
Linus Torvalds说那些对人工智能奇点深信不疑的人显然磕了药
查看>>
[小技巧] svn: 不能解析 URL
查看>>
USB_ModeSwitch 介绍
查看>>
大公司和小公司的抢人战,孰胜孰负?
查看>>
通过make编译多文件的内核模块
查看>>
如何调试Javascript代码
查看>>
皮克斯宣布开源Universal Scene Description
查看>>
复盘:一个创业项目的失败之路
查看>>
阿里巴巴宣布加入Linux基金会
查看>>
为什么你应该尝试 “全栈”
查看>>
程序员什么时候该考虑辞职
查看>>
如何写一本书?
查看>>