Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析

引出线程池

线程是并发编程的基础,前面的文章里,我们的实例基本都是基于线程开发作为实例,并且都是使用的时候就创建一个线程。这种方式比较简单,但是存在一个问题,那就是线程的数量问题。

假设有一个系统比较复杂,需要的线程数很多,如果都是采用这种方式来创建线程的话,那么就会极大的消耗系统资源。首先是因为线程本身的创建和销毁需要时间,如果每个小任务都创建一个线程,那么就会大大降低系统的效率。其次是线程本身也是占用内存空间的,大量的线程运行会抢占内存资源,处理不当很可能会内存溢出,这显然不是我们想看到的。

那么有什么办法解决呢?有一个好的思路就是对线程进行复用,因为所有的线程并不都是同一时间一起运行的,有些线程在某个时刻可能是空闲状态,如果这部分空闲线程能有效利用起来,那么就能让线程的运行被充分的利用,这样就不需要创建那么多的线程了。我们可以把特定数量的线程放在一个容器里,需要使用线程时,从容器里拿出空闲线程使用,线程工作完后不急着关闭,而是退回到线程池等待使用。这样的容器一般被称为线程池。用线程池来管理线程是非常有效的方法,用一张图片可以简单的展示出线程池的管理流程:

Executor框架

Java中也有一套框架来控制管理线程,那就是Executor框架。Executor框架是JDK1.5之后才引入的,位于java.util.cocurrent 包下,可以通过该框架来控制线程的启动、执行和关闭,从而简化并发编程的操作,这是它的核心成员类图:

Executor:最上层的接口,定义了一个基本方法execute,接受一个Runnable参数,用来替代通常创建或启动线程的方法。

ExecutorService:继承自Executor接口,提供了处理多线程的方法。

ScheduledExecutorService:定时调度接口,继承自ExecutorService。

AbstractExecutorService:执行框架的抽象类。

ThreadPoolExecutor:线程池中最核心的一个类,提供了线程池操作的基本方法。

Executors:线程池工厂类,可用于创建一系列有特定功能的线程池。

ThreadPoolExecutor详解

以上Executor框架中的基本成员,其中最核心的的成员无疑就是ThreadPoolExecutor,想了解Java中线程池的运行机制,就必须先了解这个类,而最好的了解方式无疑就是看源码。

构造函数

打开ThreadPoolExecutor的源码,发现类中提供了四个构造方法

public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue workQueue) {    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,        Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue workQueue,                              ThreadFactory threadFactory) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,            threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize,                            int maximumPoolSize,                            long keepAliveTime,                            TimeUnit unit,                            BlockingQueue workQueue,                            RejectedExecutionHandler handler) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,        Executors.defaultThreadFactory(), 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.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }复制代码

可以看出,ThreadPoolExecutor的构造函数中的参数还是比较多的,并且最核心的是第四个构造函数,其中完成了底层的初始化工作。

下面解释一下构造函数参数的含义:

corePoolSize:线程池的基本大小。当提交一个任务到线程池后,线程池会创建一个线程执行任务,重复这种操作,直到线程池中的数目达到corePoolSize后不再创建新线程,而是把任务放到缓存队列中。

maximumPoolSize:线程池允许创建的最大线程数。

workQueue:阻塞队列,用于存储等待执行的任务,并且只能存储调用execute方法提交的任务。常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。

keepAliveTime:线程池中线程的最大空闲时间,这种情况一般是线程数目大于任务的数量导致。

unit:keepAliveTime的时间单位,TimeUnit是一个枚举类型,位于java.util.concurrent包下。

threadFactory:线程工厂,用于创建线程。

handler:拒绝策略,当任务太多来不及处理时所采用的处理策略。

重要的变量

看完了构造函数,我们来看下ThreadPoolExecutor类中几个重要的成员变量:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY  = (1 << COUNT_BITS) - 1;// runState is storedinthe high-order bitsprivate static final int RUNNING    = -1 << COUNT_BITS;private static final int SHUTDOWN  =  0 << COUNT_BITS;private static final int STOP      =  1 << COUNT_BITS;private static final int TIDYING    =  2 << COUNT_BITS;private static final int TERMINATED =  3 << COUNT_BITS;// Packing and unpacking ctlprivate static int runStateOf(int c)    {returnc & ~CAPACITY; }private static int workerCountOf(int c)  {returnc & CAPACITY; }private static int ctlOf(int rs, int wc) {returnrs | wc; }复制代码

ctl:控制线程运行状态的一个字段。同时,根据下面的几个方法runStateOf,workerCountOf,ctlOf可以看出,该字段还包含了两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),并且使用的是Integar类型,高3位保存runState,低29位保存workerCount。

COUNT_BITS:值为29的常量,在字段CAPACITY被引用计算。

CAPACITY:表示有效线程数量(workerCount)的上限,大小为 (1<<29) - 1。

下面5个变量表示的是线程的运行状态,分别是:

RUNNING:接受新提交的任务,并且能处理阻塞队列中的任务;

SHUTDOWN:不接受新的任务,但会执行队列中的任务。

STOP:不接受新任务,也不处理队列中的任务,同时中断正在处理任务的线程。

TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。

TERMINATED:terminated( ) 方法执行完毕。

用一个状态转换图表示大概如下  (图片来源于www.cnblogs.com/liuzhihu/p/…):

构造函数和基本参数都了解后,接下来就是对类中重要方法的研究了。

线程池执行流程

execute方法

ThreadPoolExecutor类的核心调度方法是execute(),通过调用这个方法可以向线程池提交一个任务,交由线程池去执行。而ThreadPoolExecutor的工作逻辑也可以藉由这个方法来一步步理清。这是方法的源码:

public void execute(Runnablecommand) {if(command== null)        throw new NullPointerException();    //获取ctl的值,前面说了,该值记录着runState和workerCount    int c = ctl.get();    /*    * 调用workerCountOf得到当前活动的线程数;    * 当前活动线程数小于corePoolSize,新建一个线程放入线程池中;    * addWorker(): 把任务添加到该线程中。    */if(workerCountOf(c) < corePoolSize) {if(addWorker(command,true))return;        //如果上面的添加线程操作失败,重新获取ctl值        c = ctl.get();    }    //如果当前线程池是运行状态,并且往工作队列中添加该任务if(isRunning(c) && workQueue.offer(command)) {        int recheck = ctl.get();        /*        * 如果当前线程不是运行状态,把任务从队列中移除        * 调用reject(内部调用handler)拒绝接受任务        */if(! isRunning(recheck) && remove(command))            reject(command);        //获取线程池中的有效线程数,如果为0,则执行addWorker创建一个新线程elseif(workerCountOf(recheck) == 0)            addWorker(null,false);    }    /*    * 如果执行到这里,有两种情况:    * 1. 线程池已经不是RUNNING状态;    * 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。    * 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;    * 如果失败则拒绝该任务    */elseif(!addWorker(command,false))        reject(command);}复制代码

简单概括一下代码的逻辑,大概是这样:

1、判断当前运行中的线程数是否小于corePoolSize,是的话则调用addWorker创建线程执行任务。

2、不满足1的条件,就把任务放入工作队列workQueue中。

3、如果任务成功加入workQueue,判断线程池是否是运行状态,不是的话先把任务移出工作队列,并调用reject方法,使用拒绝策略拒绝该任务。线程如果是非运行中,调用addWorker创建一个新线程。

4、如果放入workQueue失败 (队列已满),则调用addWorker创建线程执行任务,如果这时创建线程失败 (addWorker传进去的第二个参数值是false,说明这种情况是当前线程数不小于maximumPoolSize),就会调用reject(内部调用handler)拒绝接受任务。

整个执行流程用一张图片表示大致如下:

以上就是execute方法的大概逻辑,接下来看看addWorker的方法实现。

addWorker方法

源码如下:

private boolean addWorker(Runnable firstTask, boolean core) {    retry:for(;;) {        int c = ctl.get();        int rs = runStateOf(c);        /**线程池状态不为SHUTDOWN时        * 判断队列或者任务是否为空,是的话返回false*/.if(rs >= SHUTDOWN &&            ! (rs == SHUTDOWN &&              firstTask == null &&              ! workQueue.isEmpty()))returnfalse;for(;;) {            int wc = workerCountOf(c);            /*这里可以看出core参数决定着活动线程数的大小比较对象            *  core为true表示与 corePoolSize大小进行比较            *  core为false表示与 maximumPoolSize大小进行比较          *  当前活动线程数大于比较对象就返回false*/if(wc >= CAPACITY ||                wc >= (core ? corePoolSize : maximumPoolSize))returnfalse;            // 尝试增加workerCount,如果成功,则跳出第一个for循环if(compareAndIncrementWorkerCount(c))breakretry;            // 如果增加workerCount失败,则重新获取ctl的值            c = ctl.get();  // Re-read ctl            // 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行if(runStateOf(c) != rs)continueretry;            //elseCAS failed due to workerCount change; retry inner loop        }    }    boolean workerStarted =false;    boolean workerAdded =false;    Worker w = null;    try {    //创建一个worker对象w        w = new Worker(firstTask);        //实例化w的线程t        final Thread t = w.thread;if(t != null) {            final ReentrantLock mainLock = this.mainLock;            mainLock.lock();            try {                // Recheckwhileholding lock.                // Back out on ThreadFactory failure orif// shut down before lock acquired.                int rs = runStateOf(ctl.get());if(rs < SHUTDOWN ||                    (rs == SHUTDOWN && firstTask == null)) {if(t.isAlive()) // precheck that t is startable                        throw new IllegalThreadStateException();                    // workers是一个HashSet,保存着任务的worker对象                    workers.add(w);                    int s = workers.size();if(s > largestPoolSize)                        largestPoolSize = s;                    workerAdded =true;                }            } finally {                mainLock.unlock();            }if(workerAdded) {            //启动线程                t.start();                workerStarted =true;            }        }    } finally {if(! workerStarted)            addWorkerFailed(w);    }returnworkerStarted;}复制代码

从代码中可以看出,addWorker方法的主要工作是在线程池中创建一个新的线程并执行,其中firstTask参数指定的是新线程需要执行的第一个任务,core参数决定于活动线程数的比较对象是corePoolSize还是maximumPoolSize。根据传进来的参数首先对线程池和队列的状态进行判断,满足条件就新建一个Worker对象,并实例化该对象的线程,最后启动线程。

Worker类

根据addWorker源码中的逻辑,我们可以发现,线程池中的每一个线程其实都是对应的Worker对象在维护的,所以我们有必要对Worker类一探究竟,先看一下类的源码:

private final class Worker    extends AbstractQueuedSynchronizer    implements Runnable{    /**    * This class will never be serialized, but we provide a    * serialVersionUID to suppress a javac warning.    */    private static final long serialVersionUID = 6138294804551838833L;    /** Thread this worker is running in.  Nulliffactory fails. */    final Thread thread;    /** Initial task to run.  Possibly null. */    Runnable firstTask;    /** Per-thread task counter */    volatile long completedTasks;    /**    * Creates with given first task and thread from ThreadFactory.    * @param firstTask the first task (nullifnone)    */    Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorker        this.firstTask = firstTask;        this.thread = getThreadFactory().newThread(this);    }    /** Delegates main run loop to outer runWorker  */    public voidrun() {        runWorker(this);    }    // Lock methods    //    // The value 0 represents the unlocked state.    // The value 1 represents the locked state.    protected booleanisHeldExclusively() {returngetState() != 0;    }    protected boolean tryAcquire(int unused) {if(compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());returntrue;        }returnfalse;    }    protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);returntrue;    }    public voidlock()        { acquire(1); }    public booleantryLock()  {returntryAcquire(1); }    public voidunlock()      { release(1); }    public booleanisLocked() {returnisHeldExclusively(); }    voidinterruptIfStarted() {        Thread t;if(getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {            try {                t.interrupt();            } catch (SecurityException ignore) {            }        }    }}复制代码

从Worker类的构造函数可以看出,当实例化一个Worker对象时,Worker对象会把传进来的Runnable参数firstTask赋值给自己的同名属性,并且用线程工厂也就是当前的ThreadFactory来新建一个线程。

同时,因为Worker实现了Runnable接口,所以当Worker类中的线程启动时,调用的其实是run()方法。run方法中调用的是runWorker方法,我们来看下它的具体实现:

final void runWorker(Worker w) {    Thread wt = Thread.currentThread();    //获取第一个任务    Runnable task = w.firstTask;    w.firstTask = null;    //允许中断    w.unlock(); // allow interrupts    //是否因为异常退出循环的标志,processWorkerExit方法会对该参数做判断    boolean completedAbruptly =true;    try {    //判断task是否为null,是的话通过getTask()从队列中获取任务while(task != null || (task = getTask()) != null) {            w.lock();            // If pool is stopping, ensure thread is interrupted;            //ifnot, ensure thread is not interrupted.  This            // requires a recheckinsecondcaseto deal with            // shutdownNow racewhileclearing interrupt            /* 这里的判断主要逻辑是这样:            * 如果线程池正在停止,那么就确保当前线程是中断状态;            * 如果不是的话,就要保证不是中断状态            */if((runStateAtLeast(ctl.get(), STOP) ||                (Thread.interrupted() &&                  runStateAtLeast(ctl.get(), STOP))) &&                !wt.isInterrupted())                wt.interrupt();            try {                //用于记录任务执行前需要做哪些事,属于ThreadPoolExecutor类中的方法,//是空的,需要子类具体实现                beforeExecute(wt, task);                Throwable thrown = null;                try {                //执行任务                    task.run();                } catch (RuntimeException x) {                    thrown = x; throw x;                } catch (Error x) {                    thrown = x; throw x;                } catch (Throwable x) {                    thrown = x; throw new Error(x);                } finally {                    afterExecute(task, thrown);                }            } finally {                task = null;                w.completedTasks++;                w.unlock();            }        }        completedAbruptly =false;    } finally {            processWorkerExit(w, completedAbruptly);    }}复制代码

总结一下runWorker方法的运行逻辑:

1、通过while循环不断地通过getTask()方法从队列中获取任务;

2、如果线程池正在停止状态,确保当前的线程是中断状态,否则确保当前线程不中断;

3、调用task的run()方法执行任务,执行完毕后需要置为null;

4、循环调用getTask()取不到任务了,跳出循环,执行processWorkerExit()方法。

过完runWorker()的运行流程,我们来看下getTask()是怎么实现的。

getTask方法

getTask()方法的作用是从队列中获取任务,下面是该方法的源码:

private RunnablegetTask() {//记录上次从队列获取任务是否超时    boolean timedOut =false; // Did the last poll() time out?for(;;) {        int c = ctl.get();        int rs = runStateOf(c);        // Checkifqueue empty onlyifnecessary.if(rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {        //将workerCount减1            decrementWorkerCount();returnnull;        }        int wc = workerCountOf(c);        // Are workers subject to culling?        /*timed变量用于判断线程的操作是否需要进行超时判断        *allowCoreThreadTimeOut不管它,默认是false*  wc > corePoolSize,当前线程是如果大于核心线程数corePoolSize        */        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if((wc > maximumPoolSize || (timed && timedOut))            && (wc > 1 || workQueue.isEmpty())) {if(compareAndDecrementWorkerCount(c))returnnull;continue;        }        try {        /* 根据timed变量判断,如果为true,调用workQueue的poll方法获取任务,        * 如果在keepAliveTime时间内没有获取到任务,则返回null;        * timed为false的话,就调用workQueue的take方法阻塞队列,                  * 直到队列中有任务可取。        */            Runnable r = timed ?                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :                workQueue.take();if(r != null)returnr;            //r为null,说明time为true,超时了,把timedOut也设置为truetimedOut =true;        } catch (InterruptedException retry) {        //发生异常,把timedOut也设置为false,重新跑循环            timedOut =false;        }    }}复制代码

getTask的代码看上去比较简单,但其实内有乾坤,我们来重点分析一下两个if判断的逻辑:

1、当进入getTask方法后,先判断当前线程池状态,如果线程池状态rs >= SHUTDOWN,再进行以下判断:

1)rs 的状态是否大于STOP;2)队列是否为空;

满足以上条件其中之一,就将workerCount减1并返回null,也就是表示队列中不再有任务。因为线程池的状态值是SHUTDOWN以上时,队列中不再允许添加新任务,所以上面两个条件满足一个都说明队列中的任务都取完了。

2、进入第二个if判断,这里的逻辑有点绕,但作用比较重要,是为了控制线程池的有效线程数量,我们来具体解析下代码:

wc > maximumPoolSize:判断当前线程数是否大于maximumPoolSize,这种情况一般很少发生,除非是maximumPoolSize的大小在该程序执行的同时被进行设置,比如调用ThreadPoolExecutor中的setMaximumPoolSize方法。

timed && timedOut:如果为true,表示当前的操作需要进行超时判断,并且上次从队列获取任务已经超时。

wc > 1 || workQueue.isEmpty():如果工作线程大于1,或者阻塞队列是空的。

compareAndDecrementWorkerCount:比较并将线程池中的workerCount减1

在上文中,我们解析execute方法的逻辑时了解到,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize,并且workQueue已满时,仍然可以增加工作线程。

但调用getTask()取任务的过程中,如果超时没有获取到任务,也就是timedOut为true的情况,说明workQueue已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的线程销毁掉,也就是不断的让任务被取出,让线程数量保持在corePoolSize即可,直到getTask方法返回null。

而当getTask方法返回null后,runWorker方法中就会因为取不到任务而执行processWorkerExit()方法。

processWorkerExit方法

processWorkerExit方法的作用主要是对worker对象的移除,下面是方法的源码:

private void processWorkerExit(Worker w, boolean completedAbruptly) {//是异常退出的话,执行程序将workerCount数量减1if(completedAbruptly) // If abrupt,thenworkerCount wasn't adjusted

        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;

    mainLock.lock();

    try {

        completedTaskCount += w.completedTasks;

        // 从workers的集合中移除worker对象,也就表示着从线程池中移除了一个工作线程

        workers.remove(w);

    } finally {

        mainLock.unlock();

    }

    tryTerminate();

    int c = ctl.get();

    if (runStateLessThan(c, STOP)) {

        if (!completedAbruptly) {

            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;

            if (min == 0 && ! workQueue.isEmpty())

                min = 1;

            if (workerCountOf(c) >= min)

                return; // replacement not needed

        }

        addWorker(null, false);

    }

}复制代码

至此,从executor方法开始的整个运行过程就完毕了,总结一下该流程:

执行executor  -->  新建Worker对象,并实例化线程  -->  调用runWorker方法,通过getTask()获取任务,并执行run方法  -->  getTask()方法中不断向队列取任务,并将workerCount数量减1,直至返回null  -->  调用processWorkerExit清除worker对象。

用一张流程图表示如下所示 (图片来源于www.cnblogs.com/liuzhihu/p/…):

任务队列workQueue

前面我们多次提到了workQueue,这是一个任务队列,用来存放等待执行的任务,它是BlockingQueue类型的对象,而在ThreadPoolExecutor的源码注释中,详细介绍了三种常用的Queue类型,分别是:

SynchronousQueue:直接提交的队列。这个队列没有容量,当接收到任务的时候,会直接提交给线程处理,而不保留它。如果没有空闲的线程,就新建一个线程来处理这个任务!如果线程数量达到最大值,就会执行拒绝策略。所以,使用这个类型队列的时候,一般都是将maximumPoolSize一般指定成Integer.MAX_VALUE,避免容易被拒绝。

ArrayBlockingQueue:有界的任务队列。需要给定一个参数来限制队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程 (核心线程) 执行任务,如果达到了,则将任务放入等待队列。如果队列已满,则在总线程数不到maximumPoolSize的前提下新建线程执行任务,若大于maximumPoolSize,则执行拒绝策略。

LinkedBlockingQueue:无界的任务队列。该队列没有任务数量的限制,所以任务可以一直入队,知道耗尽系统资源。当接收任务,如果当前线程数小于corePoolSize,则新建线程处理任务;如果当前线程数等于corePoolSize,则进入队列等待。

任务拒绝策略

当线程池的任务队列已满并且线程数目达到maximumPoolSize时,对于新加的任务一般会采取拒绝策略,通常有以下四种策略:

AbortPolicy:直接抛出异常,这是默认策略;

CallerRunsPolicy:用调用者所在的线程来执行任务;

DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

DiscardPolicy:直接丢弃任务;

线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow():

public voidshutdown() {    final ReentrantLock mainLock = this.mainLock;    mainLock.lock();    try {        checkShutdownAccess();        advanceRunState(SHUTDOWN);        interruptIdleWorkers();        onShutdown(); // hookforScheduledThreadPoolExecutor    } finally {        mainLock.unlock();    }    tryTerminate();}public ListshutdownNow() {        List tasks;        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            checkShutdownAccess();            advanceRunState(STOP);            interruptWorkers();            tasks = drainQueue();        } finally {            mainLock.unlock();        }        tryTerminate();returntasks;    }复制代码

代码逻辑就不一一进行解析了,总结一下两个方法的特点就是:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

ThreadPoolExecutor创建线程池实例

ThreadPoolExecutor的运行机制讲完了,接下来展示一下如何用ThreadPoolExecutor创建线程池实例,具体代码如下:

public static void main(String[] args) {    ExecutorService service = new ThreadPoolExecutor(5, 10, 300, TimeUnit.MILLISECONDS,            new ArrayBlockingQueue(5));    //用lambda表达式编写方法体中的逻辑    Runnable run = () -> {        try {            Thread.sleep(1000);            System.out.println(Thread.currentThread().getName() +"正在执行");        } catch (InterruptedException e) {            e.printStackTrace();        }    };for(int i = 0; i < 10; i++) {        service.execute(run);    }    //这里一定要做关闭    service.shutdown();}复制代码

上面的代码中,我们创建的ThreadPoolExecutor线程池的核心线程数为5个,所以,当调用线程池执行任务时,同时运行的线程最多也是5个,执行main方法,输出结果如下:

pool-1-thread-3正在执行pool-1-thread-1正在执行pool-1-thread-4正在执行pool-1-thread-5正在执行pool-1-thread-3正在执行pool-1-thread-2正在执行pool-1-thread-1正在执行pool-1-thread-4正在执行pool-1-thread-5正在执行复制代码

看到出来,线程池确实只有5个线程在工作,也就是真正的实现了线程的复用,说明我们的ThreadPoolExecutor实例是有效的。

参考:

www.cnblogs.com/liuzhihu/p/…

www.cnblogs.com/dolphin0520…

《实战Java:高并发程序设计》

现在加群即可获取详细的Java架构脑图,还有Java工程化、高性能及分布式、高性能、高架构、zookeeper、性能调优、Spring、MyBatis、Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限及相关视频资料,群号:923116658

点击链接加入群聊【Java架构解析】:https://jq.qq.com/?_wv=1027&k=5e1QsXb

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容