剖析线程池源码-1

152 阅读6分钟

线程池里的线程是如何进行回收的?

首先我们知道线程里是分"核心线程"和"非核心线程的",并且非核心线程数 = 最大线程数 - 核心线程数。

非核心线程在空闲了AliveTime之后,就会被线程池给回收,销毁。

那么核心线程数会不会被销毁呢?答案是可能会。

如果你给线程池的属性allowCoreThreadTimeOut设为true,那么核心线程其实就和非核心线程一样,空闲一段时间后也会被回收。

在具体看源码实现时,我们还需要知道”线程“是如何在线程池里面体现的。

我们可以看到ThreadPoolExecutor类里面有一个内部类Worker。

Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /** 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);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
            runWorker(this);
        }
        //....略
}
  • 可以发现Worker是继承了AQS的,是具有加锁功能的。
  • 实现了Runnable类。
  • 并且里面内置了一个Thread线程类成员属性,这个属性的初始化是通过Worker构造函数里面初始化的,具体是通过线程工厂ThreadFactory().newThread(this)创建的,并且可以发现创建时将this即Worker传进去了,而Worker又是实现了Runnable方法,本身可以看作一个任务。
  • 重写了run方法。

那么到底是谁?这个线程到底是哪个线程呢?

我们可以看看addWorker方法,可以理解为创建线程方法:

创建Worker

private boolean addWorker(Runnable firstTask, boolean core) {
        //....部分代码略
        Worker w = null;
        try {
            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());
                    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();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

可以看到首先new了一个Worker对象,并且取出Worker对象里面的Thread属性。

然后加上mainLock锁(至于为什么这里要加锁,后面会说),将Worker对象放入workers中(存储worker的HashSet集合),然后把mainLock锁释放。重点来了,然后调用Worker对象里的那个Thread.start(),开启线程了,所以真正执行任务的就是Worker类的Thread线程属性。那么具体工作任务,就是Worker类的run()方法,因为之前说了,创建Thread属性的时候,是将Worker类(实现了Runnable类)作为参数传给Thread的构造函数了,所以可以这样理解:Worker承载了Thread,而Worker又作为任务和Thread进行绑定。

让我们看看Worker具体执行了什么任务,即run()方法:

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

继续进去里面的runWorker方法

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 {
            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();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

可以发现这个任务一直处于一个while循环。一直尝试从任务队列(真正存放用户提交的任务的队列)里获取任务,如果获取得到任务就进入循环,然后又是加锁操作"w.lock()",(因为Worker实现了AQS,可以直接加锁,至于为什么加锁,后面讲),然后执行task.run(),即拿到任务队列里的任务(Runnable)进行执行。执行结束释放锁w.unlock(),继续回到while循环,继续尝试从任务队列获取任务。

我们再继续看看从任务队列里获取任务的getTask()方法:

从任务队列获取任务

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            int wc = workerCountOf(c);
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

首先会获取线程池的状态,然后判断是否处于ShutDown状态,并且任务队列里是否为空,可以发现即使目前线程状态处于ShutDown,但是任务队列仍然存有任务,那么这个线程也还是会去拿任务,继续执行。

\

然后里面有一个很关键的标志判断:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

如果允许核心线程超时回收,或者目前线程数>核心线程数

那么这个timed就为true.则去任务队列里获取任务的方式是poll,否则是take().

poll:有时限的阻塞等待,时限就是我们定义的keepAliveTime,如果超过这个时限没有获取到任务,则返回Null,那么之前线程的while循环就会退出,就会进入线程销毁方法,完成线程的回收。

take:无时限的阻塞等待,就一直死等,不会退出while循环。

总结:所以线程池并没有具体指定某个线程就是核心线程、或者非核心线程。是否需要销毁,是根据当时线程池里的状态来决定。

线程池里创建线程之前为什么要加MainLock锁?

首先我们可以注意到 MainLock是线程池ThreadPoolExecutor的一个成员属性。

并且线程池的ShutDown()方法,即销毁线程池方法里,直接使用MainLock锁:

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

image.png

SHUTDOWN状态时,不允许创建新的线程,也不允许加入新的任务,老的任务只会由老的线程去执行掉。

所以通过mainLock加锁来保证两者的互斥性。

Worker在执行真正task任务之前,为什么需要加锁?

这个问题同样与线程池执行SHUTDOWN方法有关:

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

可以看到销毁线程池里执行了interruptIdleWorkers方法,即打断空闲线程。

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

遍历每一个worker,并且拿到对应的线程

关键在于这句判断:

if (!t.isInterrupted() && w.tryLock())

如果线程没有被打断并且可以通过这个workers加上锁,那么就将此线程打断。

从这个判断我们可以看出,线程池认为如果这个Worker没有通过w.lock()加锁,那么它就是空闲的,

与我们之前看到的”执行任务之前都会使用w.lock加锁“相对应。

总结:Worker在执行真正task任务之前,需要通过w.lock()加锁的原因就是防止线程执行任务过程中,被线程池给中断。但是线程处于阻塞状态时,即空闲状态,可以被打断。