java源码学习-线程池ThreadPoolExecutor

在开始之前得先了解线程池创建线程以及使用的流程

  1. 根据corePoolSize创建初始线程, 只要有新的任务委派给线程池执行的, 就会通过线程工厂(Executors)创建一个新的线程
  2. 如果当前线程数=corePoolSize, 那么就将后续的任务加入阻塞队列, 等待worker完成任务之后从阻塞队列的队列头开始将任务出列执行
  3. 如果阻塞队列已经满了, 那么就会根据设置的maximumPoolSize来判断, 如果当前线程数小于maximumPoolSize则通过线程工厂创建新线程, 否则进行饱和策略
  4. 饱和策略就是拒绝方式, 可以选择抛出异常, 忽略, 放弃时间最早的任务转而执行新任务, 直接由提交任务的线程执行任务这四种拒绝方式

这里是我自制的execute方法流程图
在这里插入图片描述

main方法

先贴上我做实验使用的main方法

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {
        int corePoolSize = 2;
        int maximumPoolSize = 4;
        BlockingQueue<Runnable> blockingDeque = new ArrayBlockingQueue<>(2);
        RejectedExecutionHandler handle = new ThreadPoolExecutor.AbortPolicy();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 0,
                TimeUnit.SECONDS, blockingDeque, handle);

        Thread[] threads = new Thread[6];
        for(int i = 0; i < 6; i ++) {
            int finalI = i;
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("start: " + finalI);
                        Thread.sleep(1000);
                        System.out.println("end: " + finalI);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        for(Thread thread: threads) {
            threadPoolExecutor.submit(thread);
        }
        System.out.println(threadPoolExecutor.getActiveCount());
        threadPoolExecutor.shutdown();
    }
}

构造器

这里只贴一个, 其他构造方法都是直接使用这个的

参数名称作用
corePoolSize初始线程池的线程个数
maximumPoolSize线程池中最大线程个数
keepAliveTime线程存活时间
unit存活时间的单位
workQueue一个阻塞队列
threadFactory线程工厂
handler采取的饱和政策(拒绝的策略)
拒绝策略行为
AbortPolicy拒绝时会抛出RejectedExecutionException异常
DiscardPolicy直接忽略
DiscardOldestPolicy丢弃执行队列中最老的任务, 尝试提供位置给当前任务
CallerRunsPolicy交给任务提交者执行

拒绝策略默认为AbortPolicy, 构造器中前五个参数都是必填参数, 线程工厂默认为Executors中的defaultThreadFactory

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> 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;
    }

Worker内部类

Worker继承了AQS抽象队列同步器, 以及实现了Runnable接口, 可以说是一个具有锁特性的可执行任务类
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.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        // 构造器, 从工厂中获取新线程
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        // 这个任务的run方法调用在ThreadPoolExecutor中的runWorker, 后面会写到
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.
		// 以下都是重写AQS方法来实现锁
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

executor

executor是将任务提交到线程池, 这是使用线程池的第二步, 第一步当然是new一个线程池
这个方法里面有三个判断

  1. 当前正在执行的任务小于corePoolSize, 也就是线程池还有空余, 那么直接执行任务, 否则进入2
  2. 将当前任务放入阻塞队列, 等待直至线程池中有空闲线程
  3. 如果阻塞队列满了, 就会判断之前设置的maximumPoolSize最大线程个数, 如果当前执行的任务个数小于maximumPoolSize, 那么就会创建新线程去执行这个任务
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // ctl初始值是一个很大的负数, 这个负数同CAPACITY进行与运算可以得出正在运行的任务个数
        int c = ctl.get();
        // 这是第一个判断
        if (workerCountOf(c) < corePoolSize) {
        	// 在addWorker中通过true和false来判断这是第一步还是第三步
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // isRunning方法用于判断线程池是否还在工作
        // 如果还在工作就将任务加入阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 重新进行工作检测, 在并发情况下可能会出现工作状况被别的线程修改的情况
            // 如果线程池结束工作了并且阻塞队列中移除当前任务成功, 则进行饱和策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 否则判断当前正在执行的任务个数是不是0
            // 如果是则添加一个空任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 阻塞队列满了, 第三步
        else if (!addWorker(command, false))
        	// 添加任务失败则进行饱和策略
            reject(command);
    }

addWorker

这个方法是线程池中很重要的方法, 用于执行任务

private boolean addWorker(Runnable firstTask, boolean core) {
		// break标记
        retry:
        for (;;) {
            int c = ctl.get();
            // 获取当前线程池状态
            int rs = runStateOf(c);

            // 如果rs是终止状态或者其他三种状态(这些状态都不接受新任务)并且
            // rs不是终止状态或者传入的任务为空, 或者阻塞队列为空
            // 就会进入语句块, 返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                // wc是线程正在执行的任务个数
                // 如果wc大于线程池所能承受的最大数量CAPACITY = 536,870,911(二进制中29个1)
                // 否则, 看是executor的第几步来判断是否大于corePoolSize 或者maximumPoolSize
                // 如果上述两个条件满足一个则返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 使用cas操作将正在执行的任务+1, 成功则跳出最外侧循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // cas失败了, 则更新c的数值
                c = ctl.get();  // Re-read ctl
                // 判断rs状态是否改变
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	// 创建一个新任务, 能来到这一步的都不可能是进入阻塞队列的任务, 所以需要新建一个工作
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                // 这里要获取线程池的锁, 为了防止在创建工作时线程池状态被改变
                mainLock.lock();
                try {
                    // 获取rs的状态
                    int rs = runStateOf(ctl.get());
					// 这里不需要进行跟上面一样的重复判断, 因为这里是同步的
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 判断t线程是否已经存在, 因为这里需要启动线程
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 将w加入到workers中, workers是一个被final修饰的hashSet
                        // 这里提一句被final修饰的类或者对象我们是可以改变其内部值的
                        // final修饰对象时仅仅是不可改变其引用地址
                        // 因为workers的所有方法在使用前都会加上锁, 所以这里可以直接获取size
                        workers.add(w);
                        int s = workers.size();
                        // largestPoolSize初始是0, 用于记录最大的同时执行任务数
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 任务添加成功就执行线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
        	// 任务执行失败就调用添加任务失败方法来移除任务
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

runWorker

在runWorker中已经创建的Worker会一直存在, 并且会自旋, 获取阻塞队列中的任务执行就是在这个方法中, Worker最大值就是之前设置的最大线程数

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	// 这里的循环如果初始传入的Worker中无任务, 则使用getTask方法从阻塞队列中获取
        	// getTask方法里面如果返回null则会中止线程, keepAliveTime参数在这里有用到
            while (task != null || (task = getTask()) != null) {
            	// 在这里就可以看见work被锁上了
            	// 所以worker继承aqs的作用就是为了防止任务执行时被中断
            	// 所以不论是shutdown还是shutdownNow, 都无法在worker执行的时候终止worker
                w.lock();
                // 当前线程池是否大于STOP状态, 或者当前线程需要中断并清除中断状态, 并且
                // 这时的线程池状态是大于STOP的
                // 如果上述条件都满足, 那么检查该对象是否处于中断状态, 如果不处于则设置中断
                // 这里是保证线程池处于中断状态的时候, 一定设置了线程的interrupt标记
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                	// 执行前方法是由子类实现的, 在本类中是一个受保护的方法
                    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, 该Worker的完成任务数量+1
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	// task为空并且阻塞队列中无任务则关闭该Worker
            processWorkerExit(w, completedAbruptly);
        }
    }

不难发现, 只要阻塞队列中还有任务, 已经存在的Worker就不会关闭, 而是去执行阻塞队列中的任务

private void processWorkerExit(Worker w, boolean completedAbruptly) {
		// 这个标志在上文中, 如果是正常的try流程则会为false
		// 如果是异常了就会是true, 这里就会进入decrementWorkerCount方法
		// 使用cas操作将ctl- 1
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	// 更新总完成数, 在hashSet中移除该Worker
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
		// 判断线程池状态尝试终止线程池
        tryTerminate();

        int c = ctl.get();
        // 如果当前线程池状态是不需要被关闭的, 即小于STOP的
        if (runStateLessThan(c, STOP)) {
        	// 判断是否正常中止, 如果是则说明所有任务都已完成就进入该语句块
            if (!completedAbruptly) {
            	// min默认是corePoolSize, 如果允许超时则是0
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 如果min是0阻塞队列是不为空的, 那么必须要保证有一个工作线程
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                // 如果当前线程数量大于等于最小工作线程数量则直接返回
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 因为不是正常中止, 所以该Worker不该被中止, 添加一个空的Worker
            // 这样可以继续执行阻塞队列中的任务
            addWorker(null, false);
        }
    }

getTask

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
		// 这是一个死循环, 即必须返回一个元素
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 判断线程池状况, 如果下面情况同时满足则将ctl - 1
            // 1.状态>=SHUTDOWN, 即需要被中止
            // 2.状态>=STOP, 即需要被中止并且不完成已添加的任务, 或者阻塞队列为空
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
			// 获取工作线程个数
            int wc = workerCountOf(c);

            // 判断工作线程个数是否大于核心线程个数, 那临时线程是可以超时销毁的, 所以timed是true
            // allowCoreThreadTimeOut参数为是否允许核心线程超时, 默认为false
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			
			// 如果工作线程数量大于最大线程数量, 或者
			// timed是true并且timedOut是true(允许销毁核心线程或者临时线程的poll等待超时)
			// 并且工作线程个数大于1或者阻塞队列为空
			// 如果阻塞队列非空那么一定要保证有多余一个线程可以执行任务, 所以做这么个判断
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // 满足条件就将wc-1, 因为wc大于最大线程数量或者已经超时
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            	// timed为true则将阻塞队列用poll加上等待时间出列,返回到runWorker的while中执行
            	// 如果是核心线程则默认不销毁,直接使用take()
            	// 这里的poll是根据参数keepAliveTime来判断, 若等待时间超出
            	// 即阻塞队列中无任务的时间超出keepAliveTime就返回null,
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                	// 返回r, 可以给Worker执行
                    return r;
                // 这里设置timedOut=true, 然后重新自旋
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

后记

在学习ThreadPoolExecutor源码的过程中遇到最大的问题就是, 一直在想阻塞队列中的任务是如何执行的, 线程池为什么可以节省创建线程的时间, 线程池中是否创建的时候就自带线程, 这些问题在学习完源码之后就都解决了

shutdown流程

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值