通过手撸线程池深入理解其原理(下)

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


摘要:上篇实现了简单的无锁线程池,中篇实现了简单的加锁线程池,本篇着重剖析java线程池源码。

一、基础概念
其实,把下面这些参数概念理解了,看线程池源码像看helloworld一样简单。

  • corePoolSize
    核心线程数量,什么是核心线程呢?就是一创建出来就常驻内存,不退出不销毁,直观点看,代码长这样
    public void run(){
        while(true){
    	     //不停的执行任务或者等待执行任务
    	}
    }
    
  • maximumPoolSize
    最大线程数量,什么是最大线程数呢?就是这个线程池最多能同时运行的线程数量。比如核心线程数是5,最大线程数是10,那么当5个核心线程不够用的时候,会最多再创建5个线程,达到最大线程数10。那么疑问就来了,非核心线程会被回收吗?答案是不确定,根据你设置的存活时间来定。
  • keepAliveTime
    线程保持存活的时间,通俗的讲就是,某个线程在给定的时间内没有接活,那么就会退出销毁了。那么这个设置只对非核心线程有效吗?答案是否定的,线程池还有个参数可以设置核心线程也能被回收。
  • BlockingQueue
    阻塞队列,什么是阻塞队列呢?和普通的队列有什么不一样?这人家的名字不是已经告诉你了嘛,区别在于人家可以阻塞。就是当队列为空的时候可以阻塞在那里等着。来简单看看其中一个阻塞队列LinkedBlockingDeque的实现。
    public E takeFirst() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E x;
            //如果队列一直为空,那么就一直阻塞在那
            //当队列不为空了,notEmpty.await()会被唤醒
            while ( (x = unlinkFirst()) == null)
                notEmpty.await();
            return x;
        } finally {
            lock.unlock();
        }
    }
    ……
    public E pollFirst(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            E x;
            //这个和take不一样的是,可以指定等待时间,超时直接返回null
            while ( (x = unlinkFirst()) == null) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return x;
        } finally {
            lock.unlock();
        }
    }
    
  • ThreadFactory
    线程工厂,听着名字像是生产线程的,没错,就是字面意思。这是线程池将线程的创建开放出来给用户自定义,可以看个简单的例子。
    new ThreadFactory() {
       @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "我是从线程工厂出生的");
        }
    };
    
    通过线程工厂,我们能对线程进行个性化的定制,再具体的就不演示了。
  • RejectedExecutionHandler
    拒绝执行策略,通俗点就是,当任务执行不过来了,你可以自定义一个处理策略,默认的策略就是直接抛异常。但是在实际应用中,应该自定义一个比较友好的处理策略。
  • ctl
    是不是差点没认出来,这就是java线程池里到处出现的一个参数。
     private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
    可以看一下源码中的注释是如何解释这个缩写的参数的,它其实就是一个控制线程池大小和状态的参数。
    通过手撸线程池深入理解其原理(下)_第1张图片
    我们将它定义的状态给打印出来看看,就能一目了然。
    int COUNT_BITS = Integer.SIZE - 3;
    int CAPACITY = (1 << COUNT_BITS) - 1;
    int RUNNING = -1 << COUNT_BITS;
    int SHUTDOWN   =  0 << COUNT_BITS;
    int STOP       =  1 << COUNT_BITS;
    int TIDYING    =  2 << COUNT_BITS;
    int TERMINATED =  3 << COUNT_BITS;
    System.out.println("COUNT_BITS = " + COUNT_BITS);
    System.out.println("CAPACITY   = " + CAPACITY + "  = 000" + Integer.toBinaryString(CAPACITY));
    System.out.println("~CAPACITY  = " + ~CAPACITY + " = " + Integer.toBinaryString(~CAPACITY));
    System.out.println("RUNNING    = " + RUNNING + " = " + Integer.toBinaryString(RUNNING));
    System.out.println("SHUTDOWN   = " + SHUTDOWN + " = " + Integer.toBinaryString(SHUTDOWN));
    System.out.println("STOP       = " + STOP + "  = 00" + Integer.toBinaryString(STOP));
    System.out.println("TIDYING    = " + TIDYING + " = 0" + Integer.toBinaryString(TIDYING));
    System.out.println("TERMINATED = " + TERMINATED + " = 0" + Integer.toBinaryString(TERMINATED));
    
    通过手撸线程池深入理解其原理(下)_第2张图片
    可以看到,一个32位整型,容量用29位来表示,剩下的3位表示状态。只要ctl<0那么线程池就是正常运行的,如果ctl>=0就是要关闭了。
  • 自旋
    通俗点就是死循环for(;;);,可能有人会问,为什么没见过用while来自旋的?其实吧,都行,从现在来看,两者效率基本一样。
  • CAS
    CAS全称是compareAndSet,就是比较并更新的意思,这是一个原子操作,由底层汇编指令实现。使用到的汇编指令是cmpxchg,其作用是比较并交换操作数(感兴趣的百度一下这个指令的具体释义)。java是不能直接使用汇编的,那怎么办呢?java可以通过C语言间接的去使用这个指令,那么就引出了Unsafe这个类,简单的讲,这个类给你提供了类似C/C++的操作内存的方法,所以非常危险,我们如果要使用只能通过反射拿到它进行使用。
  • AQS
    AQS全称是AbstractQueuedSynchronizer,它是一个队列同步器。它的作用就像是一条管道一样,严格的控制线程的先进先出,任一时刻,有且仅有一个线程能运行,其它都会被阻塞。比如说,有2个线程,同时争夺一个资源,那么线程1先拿到了,线程2再想去拿,就拿不到,并且放到队尾排队,只有线程1释放了这个资源,出队了,线程2才能拿到。依次类推。关于AQS后续通过其它文章再深入探讨。

二、ThreadPoolExecutor源码剖析
通过上面对基础概念的解释,这里就不再重复对其构造函数分析了。
1.任务提交的三种方式

  • void execute(Runnable command)
    这种方式提交的任务,是没有返回值,也没有Future异步等待task执行结束。
  • Future submit(Runnable task)
    这种方式提交的任务,也没有返回值,有Future异步等待task执行结束。
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    ……
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    
    ……
    public interface RunnableFuture<V> extends Runnable, Future<V> {
    	void run();
    }
    
    这里对task的包装挺巧妙的,因为接口可以多继承,所以包装一个新的接口RunnableFuture继承自RunnableFuture,这样再通过中间人FutureTaskRunnableCallable包装起来,完美!
  • Future submit(Callable task)
    这种方式提交的任务,必须有返回值(也就是T不能为Void),有Future异步等待task执行结束。

这三种提交方式,最后都由execute执行。

2.线程池入口execute源码剖析

  • 代码分析
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //判断当前线程池的大小是否小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
            //如果还未达到核心线程数,那么就继续创建线程 
            if (addWorker(command, true))
                return;
            //这里尝试获取最新的线程数量
            c = ctl.get();
        }
        //如果线程池大小已经达到核心线程数,那么就放到队列中排队
        if (isRunning(c) && workQueue.offer(command)) {
            //不管是多少次检查,都是为了尽可能感知并发下的状态变化
            int recheck = ctl.get();
            //如果线程池关闭了,那么就不要再往队列里面加任务了,这里
            //尝试删除之前加入的那个任务,注意,这个操作是可能失败返回
            //false的,因为你一加进去,可能就立马被执行了。如果删除成功
            //就使用相应的拒绝策略处理
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //这个就比较耐人寻味,线程池正在运行并且大小为0的时候创建一个线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //这个就很好理解了,当核心线程满了,队列满了,就创建非核心线程
        //来处理,如果非核心线程也满了,就执行相应的拒绝策略处理了
        else if (!addWorker(command, false))
            reject(command);
    }
    
  • Q&A
    1) 第一个if已经判断当前线程池大小没有达到核心线程数了,为什么addWorker还可能返回false?
    这个问题在我没有自己手撸一个线程池的时候,我看了好多遍也没理解。我们来假设这么一个场景,核心线程数为2,最大线程数为4,多线程并发提交任务,是不是有可能会发生同时进行int c = ctl.get();或者前后相隔非常非常短的时候进行int c = ctl.get();,这导致的结果是什么呢?线程没创建出来,线程池大小没更新,大家拿到的都是一个值,都小于corePoolSize,所以需要在addWorker里面进行拦截判断(具体的下面再讲),如果已经有2个核心线程创建完了,那么剩下的2个就会返回false,继续进行下面的分支判断。
    2)在什么情况下这个workerCountOf(recheck) == 0true
    为什么线程池运行的时候,并且经过前面核心线程的创建,还会出现线程数为0的情况。这个我也还没确切的结论。

3.创建工作线程addWorker源码剖析

  • 代码分析
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            //拿到线程池运行状态
            int rs = runStateOf(c);
    		
    		//1.线程池状态大于等于SHUTDOWN
    		//2.!(线程池状态正好等于SHUTDOWN && firstTask == null && ! workQueue.isEmpty())
    		//同时满足第一个条件和第二个条件,就不用继续往下创建工作线程了
    		//通俗的解释这段代码的意思,第一个条件很容易明白,就是线程池要
    		//关闭了,不能创建工作线程了。第二个条件就得琢磨一下,就是线程
    		//池状态正好等于SHUTDOWN,并且task是空的,并且队列不为空,那
    		//肯定得再搞个线程去处理队列中剩余的任务吧。反之如果线程池状态大于
    		//SHUTDOWN,就是要立马关掉,不做任何后续处理,那当然就不需要
    		//再创建工作线程了。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
    
            for (;;) {
                //拿到当前线程池大小
                int wc = workerCountOf(c);
                //1.线程池大小超过理论最大容量
                //2.如果要创建的是核心线程,那么要判断核心线程数是否已经满了,非核心线程同理
                //只要满足1和2其中一个条件,就返回false,创建失败
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //如果前面的关卡都过了,说明可以创建,然后提前增加线程池大小
                //这里使用了CAS去增加   
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //如果修改失败了,拿到最新的值    
                c = ctl.get();  // Re-read ctl
                //判断一下线程池状态是否变化,如果有变化,就跳到外层循环
                //返回继续在内层循环使用CAS尝试增加线程池大小
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
    
        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;
                //下面这里开始加锁,因为涉及到HashSet的读写,它是线程非安全的
                mainLock.lock();
                try {
             		
                    int rs = runStateOf(ctl.get());
    				//1.线程池正常运行
    				//2.线程池状态等于SHUTDOWN并且firstTask为空,意思就是创建一个线程去消耗队列里面的任务
    				//满足以上任意一个条件都能创建
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //检查这个线程是否正在运行,因为还没进行到下面t.start(),在这里就运行了的话,明显是不合法的(具体什么场景会碰到,我也不清楚)
                        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();
                }
                if (workerAdded) {
                    //启动线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                //启动失败就进行一些处理操作
                addWorkerFailed(w);
        }
        return workerStarted;
    }
    
  • Q&A
    1)为什么需要两层for循环?
    我已经在代码注释写了个大概,第一层循环的作用是判断线程池的状态。第二层循环的作用是判断是否能创建线程,如果能就使用CAS尝试增加线程池的大小。如果失败了,并且线程池状态发生了变化,那么跳到外层循环进行判断处理,反之继续在内层循环重复上述操作。
    2)为什么需要使用CAS增加线程池大小,而不直接使用++或+1呢?
    这里就引回到execute源码剖析那里了,还记得上面分析过,如果并发提交任务,会在第一个if分支里,全部进入到addWorker里,如果这时候不加锁,会出现创建了4个核心线程的情况,使用了CAS操作,能保证只有2个核心线程被创建,其它两个会返回false。
    3)为什么需要提前加线程池大小呢?
    这样能快速的让那些不速之客离开addWorker,也能让外面还在想进来的任务死了这条心。

4.工作线程Worker源码剖析

  • 代码分析
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        private static final long serialVersionUID = 6138294804551838833L;
    	
    	//工作线程
        final Thread thread;
        //创建Worker的时候,如果传了Task,那么就直接执行这个Task,否则去队列中拿Task
        Runnable firstTask;
        
        //这就是一个统计用的
        volatile long completedTasks;
        
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //这里创建的线程,传入的Runnable就是Worker本身
            //当在外面调用t.start的时候,就会运行Woker中的run方法
            this.thread = getThreadFactory().newThread(this);
        }
    
        public void run() {
        	//真正执行的方法
            runWorker(this);
        }
    
    	//state == 0表示目前没有被线程持有,state == 1表示目前被一个线程持有,相当于被锁住了
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
    	
    	//尝试加锁,如果当前state == 0,那么尝试将state置为1,也就是加锁
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
            	//设置当前线程为这个Worker的独占线程(就是排他锁)
                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) {
                }
            }
        }
    }
    
  • Q&A
    1)为什么Worker构造函数中有这一行setState(-1)代码?
    看到Woker的interruptIfStarted方法里,有这么一个判断
    “getState() >= 0 && (t = thread) != null && !t.isInterrupted()”,state要大于等于0,才能中断这个线程。为什么呢?很好理解嘛,我都还没启动,你就给我中断,这算是人干的事?当外面调用了t.start,线程启动了,state设置为0,这时候你可以中断。

    2)为什么runWorker方法在外面?
    这个我也没想到,难道仅仅是为了Worker代码尽量简洁?
    3)为什么要继承AbstractQueuedSynchronizer,不直接使用ReentrantLock?
    我们都知道,ReentrantLock是可重入锁,但是Worker的特性,它不能使用可重入锁,为什么呢?因为它需要实现不可重入的特性去反应线程现在的执行状态。下面分析到interruptIdleWorkers方法的时候,大家就能豁然开朗了。

5.执行任务runWorker源码剖析

  • 代码分析
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        //这是防止重复执行这个task
        w.firstTask = null;
        //这里就是将state位置0的操作
        w.unlock(); // allow interrupts
        //这个变量也有点意思,考虑得比较全面,这个是防止while循环里面有捕获不到的异常,也就是非正常结束。最后会有相应的处理策略。
        boolean completedAbruptly = true;
        try {
        	//task != null就先执行它,然后才去队列里面拿task
        	//getTask有三种可能,第一个是返回task,第二个是返回null,第三个是一直阻塞(具体的分析getTask源码再讨论)
            while (task != null || (task = getTask()) != null) {
            	//执行任务的时候先加锁
                w.lock();
                
                //这种代码在线程池很常见,无非是判断当前线程池的状态,做相应的处理,可自己试着分析一下,很简单
                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;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            //运行到这里,说明没有未知异常,置为false
            completedAbruptly = false;
        } finally {
            //这就是后续的处理,里面没啥好讨论的,感兴趣可自行分析
            processWorkerExit(w, completedAbruptly);
        }
    }
    
  • Q&A
    1)为什么执行任务前要加锁?
    如果单单从这里看,确实没有加锁的必要,这一段就单纯的是同步执行task.run方法,是串行的。如果从全局看,有这么一个方法"interruptIdleWorkers",可以看看它的代码。这里也呼应了上面分析的,为什么要用AQS实现一个不可重入锁,就是为了反应worker的状态。
    //尝试中断空闲的线程
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //重点来了,这里中断之前,需要判断一下线程是否空闲,怎么判断呢?简单,我们尝试去获取锁,如果获取成功,说明那个线程没有在执行task.run,也就是空闲的了。
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
    

6.获取任务getTask源码剖析

  • 代码分析

    private Runnable getTask() {
    	//这个变量的设计,看一下中篇就知道了,很是巧妙
        boolean timedOut = false; 
    
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
    		
    		//还是判断线程池状态那一套
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
    
            int wc = workerCountOf(c);
    
            //是否允许回收线程,很好理解,如果允许核心线程被回收或者当前线程池大小大于核心线程数,都是允许线程被回收的。
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    		
    		//这一段,判断当前线程是否可以回收,如果可以,先将线程池大小减1,并且返回null,外面的循环就会退出了,线程也就退出了
    		//再来看看具体的条件
    		//1.当前线程池大小大于最大线程数
    		//2.允许线程被回收 并且 超过指定时间没获取到任务 并且 (当前线程池中还有至少2个线程 或者 队列为空了)
    		//满足1和2任意一个条件就可以回收线程了
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                //这里也是用CAS尝试去将线程池大小减1
                if (compareAndDecrementWorkerCount(c))
                    return null;
                //如果失败了,说明有其它线程成功了,那就重复上述步骤    
                continue;
            }
    
            try {
                //允许回收的话,就调用poll,它可以设置超时时间,如果指定时间内没有任务处理,timedOut就为true,说明可以回收了
                //反之就调用take一直阻塞下去
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
    
  • Q&A
    1)为什么在获取Runnable为null的时候,直接递减线程池大小,然后返回null呢?
    如果你自己手撸过线程池,那么应该会遇到这个场景,也就知道为什么了,现在我举个小例子

    try {
         boolean timed;
         try {
             lock.lock();
             //因为这里锁住了,N个线程都会顺序判断一遍,注意,这里会非常快
             timed = currentPoolSize.get() > corePoolSize;
             //if(timed && timeout) {
             //    currentPoolSize.decrementAndGet();
             //    return null;
             //}
         } finally {
             lock.unlock();
         }
         //这里执行的会比上面的慢一些,会导致什么问题呢?
         //最坏的情况下,N个线程都直接运行到了这里,并且timed=true
         //那么会有很多runnable == null
         Runnable runnable = timed ? workerQueues.poll(keepTimeAlive, TimeUnit.NANOSECONDS) : workerQueues.take();
         if(runnable != null){
             return runnable;
         }
         //这里就会递减过头了,刹不住车
         currentPoolSize.decrementAndGet();
         return null;
         //timeout = true;
     } catch (InterruptedException e) {
         //timeout = false;
     }
    

    当然了,办法肯定有的,加锁,判断等等都行,麻烦且效率低下,那还不如递减先于获取来得实在呢。

    2)InterruptedException作用是什么?
    从名字就可以看出,中断异常,那么作用就很明显了,当调用的是take阻塞的时候,当线程中断的时候,这个就会抛异常。一般什么时候中断线程呢?关闭线程池的时候。

7.线程池shutdown源码剖析

  • 代码分析
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //这就是更新线程池的状态
            advanceRunState(SHUTDOWN);
            //中断闲置的线程,还在执行的任务的让它继续收尾(上面已经分析过这个方法)
            interruptIdleWorkers();
            //开放给子类的方法
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        //尝试将线程池状态置为TERMINATED,并且还有一些相应的处理
        tryTerminate();
    }
    
    private void advanceRunState(int targetState) {
        for (;;) {
            int c = ctl.get();
            //很简单的条件,比如targetState == SHUTDOWN
            //那么c >= SHUTDOWN的话,就不需要修改了
            //如果线程池还在运行状态,那么CAS修改它的状态
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
                break;
        }
    }
    
    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //1.对于shutdown这个方法来说,调用这个方法的时候,线程池状态不可能是running了
            //2.也不可能是 >= TIDYING,应该是SHUTDOWN
            //3.如果是SHUTDOWN,并且队列中还有任务,也不行
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            
            if (workerCountOf(c) != 0) { // Eligible to terminate		
            	//这里我也不知道为什么只中断一个?    
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            	//这段代码就比较简单,就是线程池状态从TIDYING -> TERMINATED
            	//之间,可以给子类一个收尾处理的方法
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
    
  • Q&A
    1)tryTerminate作用是什么?
    这个我的理解,就是一个尝试做最后的收尾工作,给子类开放的一个方法,对于线程池关闭的主流程无任何影响。

8.线程池shutdownNow源码剖析

  • 代码分析
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            //和shutdown不同的是,不管是不是空闲线程,全部都中断了
            interruptWorkers();
            //这就是把剩余未执行的任务返回,给用户自行处理
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
    
  • Q&A
    1)shutdownNow一定能让线程退出吗?
    在这里我要重点说一下,除非程序崩溃,否则没有任何外部方式使线程强制退出,只能是通过将该线程的中断位置为1,然后由线程自行判断决定是否处理这个中断信号。而shutdown和shutdownNow唯一的区别在于,前者只给空闲线程发送中断信号,后者给所有的线程发送。可以看下面这个例子
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
    
        for (int i = 0; i < 4; i++) {
            poolExecutor.execute(() -> {
                System.out.println("当前线程:" + Thread.currentThread().getName() + " 开始");
                while (true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("接收到中断信号,退出");
                        break;
                    }
                }
                System.out.println("当前线程:" + Thread.currentThread().getName() + " 结束");
    
            });
        }
        Thread.sleep(2000);
        System.out.println("线程池关闭");
        List<Runnable> runnables = poolExecutor.shutdownNow();
        System.out.println(runnables.size());
    }
    
    通过手撸线程池深入理解其原理(下)_第3张图片
    如果使用的是shutdown,那么正在运行的线程,是无论如何都接收不到中断信息的。
    通过手撸线程池深入理解其原理(下)_第4张图片

三、总结
说实话,线程池的源码我之前看过很多次,但是每次都是懵懵懂懂,然后就是看不下去了,觉得太难了。然后突然兴起,想自己撸一个线程池,看看难度几何,写的过程,遇到很多问题,然后尝试去解决,最后发现有些思想竟然和java线程池一模一样,那些困扰我的问题也一一解开,那种感觉非常棒,哈哈。所以真就应了那一句,实践出真理啊,偷懒最终的苦还是自己承受。

你可能感兴趣的:(java)