Java基础-线程池ThreadPoolExecutor

文章详细分析了Java中的ThreadPoolExecutor线程池的构造方法,包括核心线程数、最大线程数、存活时间、阻塞队列的选择、线程工厂和拒绝策略等参数的作用及示例代码。此外,还探讨了线程池的状态转换、任务执行流程以及不同阻塞队列的特点。
摘要由CSDN通过智能技术生成

1. 构造方法

在这里插入图片描述
构造方法中有7大参数,我们一个参数一个参数来分析

1.1 核心线程数corePoolSize

	//Set containing all worker threads in pool. Accessed only when holding mainLock.
    private final HashSet<Worker> workers = new HashSet<Worker>();
    public int getPoolSize() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            return runStateAtLeast(ctl.get(), TIDYING) ? 0
                : workers.size();
        } finally {
            mainLock.unlock();
        }
    }

corePoolSize核心线程数,类似配置,是个固定值。
getPoolSize()返回的是线程池里面存放的线程。我们先看下源码,这是一个同步方法,返回的是workers.size()。从注释可以看出这个集合存储的是当前线程池所有工作的线程池。

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
                5,0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        System.out.println("初始化线程池中的线程数量:"+pool.getPoolSize());
    }
    输出结果:
    初始化线程池中的线程数量:0

刚初始化的线程池里的线程数量为0

	public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
                5,0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        System.out.println("初始化线程池中的线程数量:"+pool.getPoolSize());
        for(int i=0;i<3;i++){
            pool.execute(() -> {
                try{
                    Thread.sleep(100);
                }catch (Exception e){}
            });
            System.out.println("线程池线程数量:"+pool.getPoolSize());
        }
        Thread.sleep(10000);
        System.out.println("最终线程数量:"+pool.getPoolSize());
    }
    输出结果:
   	初始化线程池中的线程数量:0
	线程池线程数量:1
	线程池线程数量:2
	线程池线程数量:2
	最终线程数量:2

线程池的大小始终没有超过2,在子线程都执行完了之后也就是任务都执行完了,线程池的大小还是2,并没有变成0。那是不是始终不会消亡呢?再看下面代码,多设置一个参数pool.allowCoreThreadTimeOut(true)。

	public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
                5,1000, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        pool.allowCoreThreadTimeOut(true);
        for(int i=0;i<3;i++){
            pool.execute(() -> {
                try{
                    Thread.sleep(100);
                }catch (Exception e){}
            });
            System.out.println("线程池线程数量:"+pool.getPoolSize());
        }
        System.out.println("超时之前线程数量:"+pool.getPoolSize());
        Thread.sleep(10000);
        System.out.println("超时之后线程数量:"+pool.getPoolSize());
    }
    线程池线程数量:1
	线程池线程数量:2
	线程池线程数量:2
	超时之前线程数量:2
	超时之后线程数量:0

可以看到上面的代码在子线程都运行结束后,线程池的数量变成了0。allowCoreThreadTimeOut方法的作用是:设置核心线程池是否可以超时终止策略,true可以超时终止,false不终止。如果设置为true,那核心线程池的超时时间就是KeepAliveTime,并且此时要求KeepAliveTime必须要大于0。
以上代码还有一处值得思考,放入的任务超过了核心线程数2,最大线程数又是设置的5,为什么没有扩容呢?我们继续往下看。

1.2 最大线程数maximumPoolSize

最大线程数,顾名思义就是当前线程池能同时工作的最大任务数,那什么时候会从核心线程数扩容到最大线程数呢?

public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
                5,0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<7;i++){
            pool.execute(() -> {
                try{
                    Thread.sleep(100);
                }catch (Exception e){}
            });
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
        }
        Thread.sleep(100);
        System.out.println("超时之前线程数量:"+pool.getPoolSize());
        Thread.sleep(10000);
        System.out.println("超时之后线程数量:"+pool.getPoolSize());
    }
    输出结果:
    当前放入线程池任务数1线程池线程数量:1
	当前放入线程池任务数2线程池线程数量:2
	当前放入线程池任务数3线程池线程数量:2
	当前放入线程池任务数4线程池线程数量:2
	当前放入线程池任务数5线程池线程数量:3
	当前放入线程池任务数6线程池线程数量:4
	当前放入线程池任务数7线程池线程数量:5
	超时之前线程数量:5
	超时之后线程数量:2

从上面代码运行的输出结果可以看出,当放入线程池的任务数量变成5的时候,线程池开始扩容。那为什么是放第五个任务的时候扩容的呢?这里的核心线程数是2,(阻塞的工作队列)workQueue设置的也是2,也就是说当这两个都满了之后才会扩容。从最终的线程数可以看出,扩容的线程数,在任务结束后会自动消亡。

1.3 存活时间keepAliveTime

这个参数比较简单,空闲的线程等待work的时间,超过这个时间没有work,扩容的线程池会超过该时间会消亡,如果设置allowCoreThreadTimeOut为true,那么核心线程池也会消亡。

    /**
     * Timeout in nanoseconds for idle threads waiting for work.
     * Threads use this timeout when there are more than corePoolSize
     * present or if allowCoreThreadTimeOut. Otherwise they wait
     * forever for new work.
     */
    private volatile long keepAliveTime;

1.4 存活时间单位TimeUnit

线程池内部运行都是通过纳秒来进行,对外使用者可以自己定义时间单位,算是一种友好交互吧。
在这里插入图片描述

1.5 阻塞队列BlockingQueue

从上面可知,当核心线程池满了后,新的任务工作将会放到阻塞队列中去。下面我们来看一下java提供了哪些队列,队列的具体实现就不详细说明了,后面单独记录一篇。

1.5.1 ArrayBlockingQueue

ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。队列构造方法需要指定队列长度(capacity)和进出队列顺序(fair),有点类似公平锁和非公平锁。如果为true的话ArrayBlockingQueue 内部以 (先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。否则出队列的顺序是不保证的。

    /**
     * Creates an {@code ArrayBlockingQueue} with the given (fixed)
     * capacity and the specified access policy.
     *
     * @param capacity the capacity of this queue
     * @param fair if {@code true} then queue accesses for threads blocked
     *        on insertion or removal, are processed in FIFO order;
     *        if {@code false} the access order is unspecified.
     * @throws IllegalArgumentException if {@code capacity < 1}
     */
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

那么问题来了,当数组队列满了之后,新进来的任务是直接扩容执行,还是将队列头部的任务取出来执行,然后将新任务放到队列尾部中去呢?

	public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
                5,0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<7;i++){
            int j = i;
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
            pool.execute(() -> {
                try{
                    Thread.sleep(10000);
                    System.out.println("第"+(j+1)+"个线程执行了");
                }catch (Exception e){}
            });
        }
    }
    输出结果:
    当前放入线程池任务数1线程池线程数量:0
	当前放入线程池任务数2线程池线程数量:1
	当前放入线程池任务数3线程池线程数量:2
	当前放入线程池任务数4线程池线程数量:2
	当前放入线程池任务数5线程池线程数量:2
	当前放入线程池任务数6线程池线程数量:3
	当前放入线程池任务数7线程池线程数量:45个线程执行了
	第2个线程执行了
	第1个线程执行了
	第6个线程执行了
	第7个线程执行了
	第4个线程执行了
	第3个线程执行了

上面的运行结果可以看出3,4线程被放入阻塞队列中的,最后才被执行了。

1.5.2 LinkedBlockingQueue

一个有界阻塞队列,如果创建的时候不指定队列长度默认队列长度为Integer.MAX_VALUE。
基于链表实现的队列,不能保证出队列的先后顺序。

1.5.3 PriorityBlockingQueue

一个由数组结构组成的具有优先级的无界阻塞队列,优先级通过参数Comparator实现。默认队列长度是11,不同于ArrayBlockingQueue的长度是固定的,该队列的是可以动态扩容的。当然虽然说是无界,但是受JVM内存限制,不可无线大,会导致OOM。有任务要执行,可以对任务加一个优先级的权重,这样队列会识别出来,对该任务优先进行出队。

1.5.4 SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程。

public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
                5,0, TimeUnit.MILLISECONDS,
                new SynchronousQueue<>(), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<5;i++){
            int j = i;
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
            pool.execute(() -> {
                try{
                    Thread.sleep(10000);
                    System.out.println("第"+(j+1)+"个线程执行了");
                }catch (Exception e){}
            });
            System.out.println("当前线程池数量"+pool.getPoolSize());
        }
    }
    输出结果:
    当前放入线程池任务数1线程池线程数量:0
	当前线程池数量1
	当前放入线程池任务数2线程池线程数量:1
	当前线程池数量2
	当前放入线程池任务数3线程池线程数量:2
	当前线程池数量3
	当前放入线程池任务数4线程池线程数量:3
	当前线程池数量4
	当前放入线程池任务数5线程池线程数量:4
	当前线程池数量53个线程执行了
	第4个线程执行了
	第1个线程执行了
	第2个线程执行了
	第5个线程执行了

使用SynchronousQuene队列,线程并没有被存储,当有超过核心线程池的任务,直接扩容了。

1.6 线程工厂ThreadFactory

用于生产线程的方法,定义了一个接口,支持我们自己扩展,源码也比较简单。

public interface ThreadFactory {
    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

我们可以自己实现这个接口,也可以使用Executors中默认的线程工厂。

	static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

1.7 拒绝策略RejectedExecutionHandler

基于上面讨论的,我们还有一个问题要讨论,如果队列满了,线程池也满了,这个时候还有新任务进来会发生什么呢?
这里会执行拒绝策略,和线程工厂一样,定义了一个接口我们可以自己实现策略,当然默认也提供了4中策略。

public interface RejectedExecutionHandler {
    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

1.7.1 AbortPolicy

对于超过线程池最大任务后,直接抛出异常。

    public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

拒绝策略,当线程池和队列都满了之后,新任务进来会报错。但是不影响已经放入的线程执行完毕。

public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
                2,1, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<4;i++){
            int j = i;
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
            pool.execute(() -> {
                try{
                    Thread.sleep(1000);
                    System.out.println("第"+(j+1)+"个线程执行了");
                }catch (Exception e){}
            });
        }
        try{
            Thread.sleep(10000);
        }catch (Exception e){}
        pool.execute(()->{
            System.out.println("异常之后线程池还可以执行任务吗");
        });
    }
    当前放入线程池任务数1线程池线程数量:0
	当前放入线程池任务数2线程池线程数量:1
	当前放入线程池任务数3线程池线程数量:1
	当前放入线程池任务数4线程池线程数量:2
	Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.joodroid.web.controller.business.MyArrayList$$Lambda$1/125993742@e2d56bf rejected from java.util.concurrent.ThreadPoolExecutor@244038d0[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
		at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
		at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
		at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
		at com.joodroid.web.controller.business.MyArrayList.main(MyArrayList.java:16)3个线程执行了
	第1个线程执行了
	第2个线程执行了

这里我们最多能容纳3个线程(活动任务2,队列任务1),当放入第四个的时候报错了。并且当我们现有任务执行完后,在异常后再放入新任务的时候已经不再执行,所以实际使用过程中尽量避免线程池报错,包括业务代码报错需要手动处理,否则后面正常的业务也不能够被执行。

1.7.2 CallerRunsPolicy

源码逻辑比较简单,当线程池未关闭,则直接执行当前任务得run方法。否则直接丢弃了。

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

最大线程池2,队列1,当放入得任务数量大于3了,后续得线程任务都是由线程调用者自己直接执行任务。

	public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
                2,1, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        for(int i=0;i<5;i++){
            int j = i;
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
            pool.execute(() -> {
                try{
                    if(j<4){
                        Thread.sleep(1000);
                    }
                    System.out.println(Thread.currentThread().getName()+"第"+(j+1)+"个线程执行了");
                }catch (Exception e){}
            });
        }
    }
	运行结果:
	当前放入线程池任务数1线程池线程数量:0
	当前放入线程池任务数2线程池线程数量:1
	当前放入线程池任务数3线程池线程数量:1
	当前放入线程池任务数4线程池线程数量:2
	main第4个线程执行了
	当前放入线程池任务数5线程池线程数量:2
	main第5个线程执行了
	pool-1-thread-23个线程执行了
	pool-1-thread-11个线程执行了
	pool-1-thread-22个线程执行了

1.7.3 DiscardPolicy

源码逻辑更简单,就是啥也不干,就当没任务放进来。

    public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

如下执行结果,任务4和5被当前线程池抛弃了。

public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
                2,1, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy());
        for(int i=0;i<5;i++){
            int j = i;
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
            pool.execute(() -> {
                try{
                    if(j<4){
                        Thread.sleep(1000);
                    }
                    System.out.println(Thread.currentThread().getName()+"第"+(j+1)+"个线程执行了");
                }catch (Exception e){}
            });
        }
        Thread.sleep(10000);
        System.exit(-1);
    }
    输出结果:
    当前放入线程池任务数1线程池线程数量:0
	当前放入线程池任务数2线程池线程数量:1
	当前放入线程池任务数3线程池线程数量:1
	当前放入线程池任务数4线程池线程数量:2
	当前放入线程池任务数5线程池线程数量:2
	pool-1-thread-23个线程执行了
	pool-1-thread-11个线程执行了
	pool-1-thread-22个线程执行了
	Disconnected from the target VM, address: '127.0.0.1:58073', transport: 'socket'

1.7.4 DiscardOldestPolicy

将队列中第一元素弹出,将新任务加入到线程池中。

	public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

第3,4个任务被抛弃了。

	public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1,
                2,1, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        for(int i=0;i<5;i++){
            int j = i;
            System.out.println("当前放入线程池任务数"+(i+1)+"线程池线程数量:"+pool.getPoolSize());
            pool.execute(() -> {
                try{
                    if(j<4){
                        Thread.sleep(1000);
                    }
                    System.out.println(Thread.currentThread().getName()+"第"+(j+1)+"个线程执行了");
                }catch (Exception e){}
            });
        }
        Thread.sleep(10000);
        System.exit(-1);
    }
    输出结果:
    当前放入线程池任务数1线程池线程数量:0
	当前放入线程池任务数2线程池线程数量:1
	当前放入线程池任务数3线程池线程数量:1
	当前放入线程池任务数4线程池线程数量:2
	当前放入线程池任务数5线程池线程数量:2
	pool-1-thread-23个线程执行了
	pool-1-thread-11个线程执行了
	pool-1-thread-25个线程执行了

2.常量参数得定义

int型变量一共有32位,线程池的五种状态runState至少需要3位来表示,故workCount只能有29位。
TERMINATED(011) > TIDYING(010) > STOP(001) > SHUTDOWN(000)> RUNNING(101)

2.1 COUNT_BITS

一个常量,用于后续计算得一个常用值,大小是29。

private static final int COUNT_BITS = Integer.SIZE - 3;

2.2 RUNNING

线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
-2的29次方。(首位1表示负数)
1010 0000 0000 0000 0000 0000 0000 0000高3位,101表示运行状态

private static final int RUNNING    = -1 << COUNT_BITS;

2.3 SHUTDOWN

线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
0,不理解为啥还要左移,直接定义成0不好吗?,高3位,000表示SHUTDOWN状态
0000 0000 0000 0000 0000 0000 0000 0000

private static final int SHUTDOWN   =  0 << COUNT_BITS;

2.4 STOP

线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
2的29次方。
0010 0000 0000 0000 0000 0000 0000 0000,高3位,001表示STOP状态

private static final int STOP       =  1 << COUNT_BITS;

2.5 TIDYING

当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
2的30次方
0100 0000 0000 0000 0000 0000 0000 0000****高3位010

private static final int TIDYING    =  2 << COUNT_BITS;

2.6 TERMINATED

线程池彻底终止,就变成TERMINATED状态。
2的30次方加上2的29次方
0110 0000 0000 0000 0000 0000 0000 0000****高3位011

private static final int TERMINATED =  3 << COUNT_BITS;

2.6 CAPACITY

0001 1111 1111 1111 1111 1111 1111 1111,低29位表示线程池最大数量

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

2.7 ctl

包含了状态和线程数量,高三位是状态,低29位置是数量,原子常量,ctlOf方法是两个数的或运算,所以相当于初始化状态为RUNNING和设置初始化线程数为0,相当于加法运算。
1010 0000 0000 0000 0000 0000 0000 0000

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

3.execute方法

这里已添加第一个线程任务为例。

public void execute(Runnable command) {
		//判断入参不能为空,否则报空指针错误
        if (command == null)
            throw new NullPointerException();
        //获取当前线程池状态,ctl值为:1010 0000 0000 0000 0000 0000 0000 0000
        int c = ctl.get();
        //取ctl的低29位即正在运行的线程,判断当前线程池的工作任务数是否小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
        /*将当前任务添加到现有任务中,第二个参数表示是否使用核心线程池去运行(true表示是,false表示否),如果添加成功则直接返回结束当前方法。	
         *addWorker我们后面分析。
        */
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        /*第一个判断当前线程池是不是运行状态即当前c的值是否小于0,第二个判断当前任务能不能成功的放入队列中。当前c的值为一个负数,小于0所以
        * isRunning(c)的结果为true,后面的结果取决于新建线程时的入参队列。
		*/
        if (isRunning(c) && workQueue.offer(command)) {
        	//如果线程池是运行状态,并且能成功放入队列中,再获取一次线程池状态
            int recheck = ctl.get();
            //二次检查当前线程池的状态是不是运行,避免上面放入队列的过程中,线程状态改变,如果线程停止运行并且移除队列成功就执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            /*这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在
            *执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池
            *在RUNNING状态下必须要有一个线程来执行任务
            */
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //线程池非运行状态,或者运行状态下放入队列失败,那就使用非核心线程执行当前任务
        else if (!addWorker(command, false))
        	//如果非核心线程不能执行当前任务,则执行拒绝策略
            reject(command);
    }

看完代码画个图加深一下印象。
在这里插入图片描述

4.addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
		//跳出循环的标识,可以break跳出多重循环,或者指定continue的位置
        retry:
        // 死循环执行逻辑。确保多线程环境下在预期条件下退出循环。
        for (;;) {
            //取出线程池状态和线程数量信息
            int c = ctl.get();
            //只取高3位,也就是只获取线程池状态
            int rs = runStateOf(c);
            //rs是当前线程池的状态,只有运行状态是负数,其它状态均非负,那这里也就说只要不是线程池不是运行状态,就返回false。
            //在线程池状态不是运行的前提下:如果线程池状态是SHUTDOWN,传入的任务为空,或者队列不是空,三者同时满足的情况下才会继续后面的流程
            //那这里为什么会允许添加空的任务呢?我们继续往下看(上面调用addWorker也有说明)。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
			
            for (;;) {
            	//获取正在运行的线程数
                int wc = workerCountOf(c);
                //大于等于最大线程数返回fasle,大于等于核心线程数(入参要求使用核心线程去运行的情况下)或者最大线程数返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //使用cas,将工作线程数加1,如果成功跳出循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //任务数cas加1失败,重新获取一次线程状态
                c = ctl.get();  // Re-read ctl
                //如果状态已改变,重新执行外层循环,如果没改变执行内部循环
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
		/* 经过以上循环分析基本可以得出,外层循环用于线程池状态判断,而内存循环用于是否能成功添加任务。
		 * 代码能执行到这,说明ctl中线程数已经加1了,现在我们需要做的就是把真正的任务加到线程池中去
		*/
		//定义任务开始标志
        boolean workerStarted = false;
        //定义任务添加成功标志
        boolean workerAdded = false;
        Worker w = null;
        try {
        	//创建一个线程任务,Worker为一个私有的内部内
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
            	//上锁啦,核心逻辑上锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    //再次获取线程状态
                    int rs = runStateOf(ctl.get());
					//如果线程为运行状态,或者SHUTDOWN状态下,任务为空则将当前work添加到线程池中的workes集合中
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //如果线程在未启动前就已运行将抛出异常
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //将当前任务放入线程池的任务集合中
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        //添加标识置为成功
                        workerAdded = true;
                    }
                } finally {
                //解锁
                    mainLock.unlock();
                }
                //添加成功则启动当前线程,并将启动状态置为true
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
        	//启动失败(或者添加任务失败),
            if (! workerStarted)
            	//后面看这个方法
                addWorkerFailed(w);
        }
        return workerStarted;
    }

5.addWorkerFailed方法

能进入这个代码,前提是执行了addWorker的部分逻辑,需要结合addWorker一起看。什么情况下会出现这种问题呢?在成功增加活跃线程数后并成功new Worker后,线程池状态改变为 > SHUTDOWN,既不可接受新任务,又不能执行任务队列剩余的任务,此时线程池应该直接停止。

	private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        //上锁
        mainLock.lock();
        try {
            if (w != null)
            //从队列中移除添加失败的任务,当然之前不一定放进去了,就算没放进去,做一次移除也没关系
                workers.remove(w);
            //addWorker的逻辑已经给任务数加1了,这里做CAS减法操作
            decrementWorkerCount();
            //看方法名,试图将线程池置为TERMINATED状态,我们接下来看这个方法
            tryTerminate();
        } finally {
        //解锁
            mainLock.unlock();
        }
    }

6.tryTerminate方法

让线程池进入TERMINATED状态。

final void tryTerminate() {
        for (;;) {
        	//获取当前线程池状态
            int c = ctl.get();
           	//以下三个条件,满足其一都将直接返回
            //如果是运行状态,直接返回
            //如果是TIDYING和TERMINATED状态,直接返回
            //如果是SHUTDOWN状态,并且队列不是空的,直接返回
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            //工作线程数不等于0
            if (workerCountOf(c) != 0) { // Eligible to terminate
            //如果活跃线程数不为0,中断所有的worker线程,后面详细说明这个方法
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            // 首先通过 CAS 将 ctl 改变成 (rs=TIDYING, wc=0),因为经过上面的判断保证了当先线程池能够达到这个状态。
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                    // 钩子函数,在ThreadPoolExecutor 中该方法是空的,用户可以通过继承 ThreadPoolExecutor 实现自定义的方法。
                        terminated();
                    } finally {
                    // 将 ctl 改变成 (rs=TERMINATED, wc=0),此时线程池将关闭。
                        ctl.set(ctlOf(TERMINATED, 0));
                        //最后调用 termination.signalAll 激活因调用条件变量 termination 的 await 系列方法而被阻塞的所有线程,唤醒后得知线
                        //程池状态为TERMINATED后也会退出。
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

7.interruptIdleWorkers方法

中断线程中的任务,如果入参为true表示只中断一个。代码逻辑比较简单

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        //上全局锁
        mainLock.lock();
        try {
        //循环当前线程池中所有的任务
            for (Worker w : workers) {
                Thread t = w.thread;
                //如果任务中的线程不是中断状态,并且可以成功获取worker的线程锁,那就中断线程。
                //也就是说获取不到worker线程的锁,就不会中断,那什么情况下能成功获取锁呢?后面我分析worker的时候再来看
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                    //释放worker线程锁
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
        //解全局锁
            mainLock.unlock();
        }
    }

8.interruptWorkers方法

interruptWorkers本身逻辑不复杂,就是上锁后循环中断任务。核心逻辑在interruptIfStarted方法中,我们继续往下看

    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

这是work内部的一个方法。

	void interruptIfStarted() {
            Thread t;
            //这里判断只要worker是运行状态,并且线程不为空没有被打断就中断当前线程,这里并没有interruptIdleWorkers方法中
            //尝试获取锁成功才去打断。
            // state >= 0表示worker已经启动
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

9.shutdown&shutdownNow方法

shutdown: 关闭线程池中所有空闲Worker线程,改变线程池状态为SHUTDOWN。
shutdownNow:关闭线程池中所有Worker线程,改变线程池状态为STOP,并返回所有正在等待处理的任务列表。
前面也说过了SHUTDOWN和STOP的区别,前者会执行完队列中任务,而后者既不允许接受新任务,也不允许执行剩余的任务,因此需要关闭所有Worker线程,包括正在运行的。

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        //上全局锁
        mainLock.lock();
        try {
       		 //校验权限,一般默认情况下jvm不开启安全校验
            checkShutdownAccess();
            //CAS将线程池状态设置成SHUTDOWN
            advanceRunState(SHUTDOWN);
            //上面已经分析过了,主要区别一下下面shutdownNow中使用的interruptWorkers方法
            interruptIdleWorkers();
            //ThreadPoolExecutor为空
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
        	//解锁
            mainLock.unlock();
        }
        //尝试将线程状态设置为TERMINATED
        tryTerminate();
    }
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //设置线程状态为STOP
            advanceRunState(STOP);
            //注意和interruptIdleWorkers方法的区别
            interruptWorkers();
            //获取队列中剩余的所有任务
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

10. 小小总结一下

上述interruptIdleWorkers,interruptWorkers,shutdown,shutdownNow等方法的核心逻辑在Worker对象中,以及分析到现在任务是如何执行的,以及队列中的任务是什么时候执行的都没有涉及到。还有被打断的任务什么时候被移出worker列表也没体现。接下来我们看内部Worker对象。

11. 内部内Worker

11.1 类定义

当前类继承了AQS,并实现了Runnable接口。Worker实现了Runnable接口,说明Worker是一个任务;Worker又继承了AQS,说明Worker同时具有锁的性质,但Worker并没有像ReentrantLock等锁工具使用了CLH的功能,因为线程池中并不存在多个线程访问同一个Worker的场景,这里只是使用了AQS中状态维护的功能。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable

11.2 构造方法

每个Worker对象会持有一个工作线程 thread,在Worker初始化时,通过线程工厂创建该工作线程并将自己作为任务传入工作线程当中。因此,线程池中任务的运行其实并不是直接执行提交任务的run()方法,而是执行Worker中的run()方法,在该方法中再执行提交任务的run()方法。我们接下来先看Worker的run方法。

		Worker(Runnable firstTask) {
			//继承的队列中的方法,该状态表示当前work还未开始工作
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //通过当前类创建线程对象,并给内部线程属性赋值
            this.thread = getThreadFactory().newThread(this);
        }

11.3 Worker的run方法

特别注意Worker的run方法和传入的task中run方法的区别,task的run方法才是我们真正的实现业务的方法。
worker的run方法很简单,托全局的runWorker()方法执行具体逻辑。

        public void run() {
            runWorker(this);
        }

12 runWorker方法

死循环从队列中取出task依次执行,后面我们再看什么情况下可以从队列中取出,处理当前的worker。

final void runWorker(Worker w) {
		//获取当前线程
        Thread wt = Thread.currentThread();
        //复制提交的任务,并将 Worker 中的 firstTask 置为 null,便于下一次重新赋值。
        Runnable task = w.firstTask;
        //将work中的task置空
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	//这里循环获取task,我们后面看task的获取逻辑
            while (task != null || (task = getTask()) != null) {
                w.lock();
               
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                //空实现,可以继承ThreadPoolExecutor来实现该方法,在执行task之前,统一执行某些逻辑
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    //task的run方法并不是通过start方法去执行线程来调用的,而是直接调用的
                        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 {
                    //空实现,可以继承ThreadPoolExecutor来实现该方法
                        afterExecute(task, thrown);
                    }
                } finally {
                	//便于垃圾回收
                    task = null;
                    //当前worker处理的总task加1
                    w.completedTasks++;
                    //解锁,当前work的锁
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	//能执行到这,说明当前得worker不需要了
            processWorkerExit(w, completedAbruptly);
        }
    }

13 processWorkerExit方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
		//这里为true只有一种情况,那就是执行worker得时候异常了,将worker得计数减一
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();
		
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	//将当前worker完成得任务量,汇总到总任务计数中
            completedTaskCount += w.completedTasks;
            //从woerker集合中移除
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        int c = ctl.get();
        //线程池状态还能够继续处理任务
        if (runStateLessThan(c, STOP)) {
        	//如果worker正常结束
            if (!completedAbruptly) {
            	//是否允许核心线程池超时,允许得话最小得线程数就0,否则为核心线程数
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                //如果是0,判断任务队列中是否有任务,如果有就赋值为1,表示队列中得任务至少需要一个线程来执行
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                 //如果正在工作得队列大于这个最小值(0或者核心线程池),那就直接结束当前方法,否则执行下面的addWorker增加
                 //线程
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            //非正常结束得,添加一个新得worker到集合中
            addWorker(null, false);
        }
    }

14 getTask方法-重要

能保持线程池中的线程一直存活的原理在这个方法中,以及非线程池的超时和核心线程池的超时都在这个方法中实现的。

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            //如果线程池状态为不能够再处理任何任务得状态,则将工作线程减1,这个时候返回null,这时runworker得循环就结束了
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
			//获取工作线程数
            int wc = workerCountOf(c);

            //允许超时消亡或者大于核心线程数,当前值则为true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//工作线程大于最大线程,当前worker肯定要消亡得
			//工作线程大于核心线程,当前worker执行完自己的task后也会消亡,也就是说非核心线程不会处理队列中得task
			//小于核心线程(核心线程池不超时消亡allowCoreThreadTimeOut=false),那就去队列中获取task
			//小于核心线程(核心线程池超时消亡allowCoreThreadTimeOut=true),有其它存活的worker或者队列为空,那么当前worker消亡
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                //如果worker减1成功就返回null,否则继续循环
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            /*
	         * 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null;
	         * 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。所以如果核心线程池以满,新进来的task会先放到队列中去
	         * 然后核心线程池去队列中取task进行处理。
	         */
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值