线程池揭秘

156 阅读9分钟

1. 线程池的属性

属性作用
ctl用来表示线程池的状态和数量,高三位表示线程池的状态,其余的位数表示线程池的数量
RUNNINGctl的高三位,补码为111,表示-1吗, 线程池处于此状态时可以接受任务,可以执行任务队列的任务
SHUTDOWNctl的高三位,补码是000,表示0,线程池处于此状态时不可以接收任务,但可以执行任务队列的任务
STOPSTOP的状态码是001,表示1,线程池处于此状态时不可以接受任务,还会清空任务队列,以及中断正在执行的任务
TIDYINGTYDYING的状态码是002,表示2,所有的任务都执行完毕后,(同时也涵盖了阻塞队列中的任务),当前线程池中的活动都线程数量降为0,将会调用terminated方法。
TERMINATEDTERMINTED状态码是003,表示3,当线程池执行了terminated的方法后,状态会变成TERMINATED
  • 源码真相
// 线程池的状态数字
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS=29,,这29位用来表示线程池中线程数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// CAPAITY就是从第一位到第29位都是1,ctl & CAPACITY就是线程数量的多少
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// ~CAPACITY就是高三位为1,ctl & ~CAPACITY就是线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 线程池的线程数量方法
private static int workerCountOf(int c)  { return c & CAPACITY; }
// RUNNING状态补码是111,表示-1,线程池处于此状态时可以接受任务,可以执行任务队列的任务
private static final int RUNNING    = -1 << COUNT_BITS;
// SHUTDOWN的状态补码是000,表示0,线程池处于此状态时不可以接收任务,但可以执行任务队列的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// STOP的状态码是001,表示1,线程池处于此状态时不可以接受任务,还会清空任务队列,以及中断正在执行的任务
private static final int STOP       =  1 << COUNT_BITS;
// TYDYING的状态码是002,表示2,所有的任务都执行完毕后,(同时也涵盖了阻塞队列中的任务),当前线程池中的活动都线程数量降为0,将会调用terminated方法。
private static final int TIDYING    =  2 << COUNT_BITS;
// TERMINTED状态码是003,表示3,当线程池执行了terminated的方法后,状态会变成TERMINATED
private static final int TERMINATED =  3 << COUNT_BITS;

2. 线程池的参数

参数意义
corePoolSize核心线程数,当加入一个任务交给线程池执行时,如果线程池中线程的数量没有达到核心线程数,那么就会新建一个线程执行此任务
maximumPoolSize最大线程数,当线程池中的线程数量大于此值时,不会再增加线程,当线程池的线程数量处于核心线程和最大线程数之间时,就会将新添加的任务加入到任务队列
workQueue任务队列,用于存储任务Runnable
keepAliveTime非核心线程的空闲存活时间,也就是当线程数大于核心线程数时,如果某个线程空闲了keepAliveTime这么久,就会去除此线程
unitkeepAliveTime的单位
threadFactory线程工厂,利用指定的工厂创建线程,可以方便追踪我们的线程
handler拒绝策略,当线程池中线程数大于最大线程数值时或者是线程池处于非RUNNING时,会拒绝当前加入的任务,调用handler.reject()
  • 源码真相
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler);

3. execute方法

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // 还记得ctl表示啥吧,高三位表示线程池的状态,其余的位数表示线程池中的线程数量
    int c = ctl.get();
    // 当线程池中的线程数小于核心线程数时
    if (workerCountOf(c) < corePoolSize) {
    // 新添加一个线程来处理任务command
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 线程数大于核心线程数将任务添加到任务队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 检查线程池的状态,如果不是RUNNING那么就删除该任务并拒绝
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果此时线程池中没有线程,那么添加一个非核心线程来处理任务false代表非核心线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果任务队列满了,创建新的线程进行处理任务
    // 那为什么没有判断当前线程数和最大线程数呢?其实这在addWorker中判断了
    else if (!addWorker(command, false))
        reject(command);
}

addWorker方法---创建线程并启动

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

        //这个判断条件可以拆分成(rs>=SHUTDOWN && (rs!=SHUTDOWN || firstTask!=null || workQueue.isEmpty())),任意条件成立就不会添加新线程并且返回
        // ①:rs!=SHUTDOWN为真,那么rs>SHUTDOWN,不能接收新任务
        // ②:rs!=SHUTDOWN为假,那么rs==SHUTDOWN且添加的任务不为null时,由于在SHUTDOWN状态不能处理新任务,所以直接返回,当firstTask为null时创建的线程会处理任务队列(如果任务队列不为空)的任务,所以firstTask为null不会返回
        // ③:rs!=SHUTDOWN为假,那么rs==SHUTDOWN且任务队列为空,且firstTask为null,由于此时任务队列为空,创建的新线程没有作用,所以直接返回
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        // 自循环创建线程
        for (;;) {
            // 获取线程池中线程数量
            int wc = workerCountOf(c);
            // 如果创建的是核心线程,那么wc>核心线程数直接返回
            // 如果创建的是非核心线程,那么wc>最大线程数直接返回
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 利用cas增加线程池的线程数量,成功就退出循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            // 如果状态变为了非RUNNING,就进入上外循坏重新判断
            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 {
                int rs = runStateOf(ctl.get());
                // 如果线程池的状态为RUNNING
                // 或者是线程池的状态为SHUTDOWN且firstTask为null
                // 为什么要多次检查线程池的状态呢,因为线程池的操作环境也可能是多线程的,不能保证线程池的状态一直不变
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 如果线程已经start了,抛异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 添加线程到workers中,workers是一个hashset
                    // 所以需要加锁
                    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;
}

总结一下,什么时候添加新线程会失败

  • 当线程池的状态大于等于STOP时
  • 当线程池的状态是shutdown时并且firstTask不为空,由于shutdown时的线程池不能处理新的任务,所以添加worker失败
  • 当线程池的状态是shutdown时并且任务队列为null时,shutdown状态的线程池可以执行任务队列中的任务,但是任务队列已经没有任务了,所以不能添加worker

runWorkers方法----线程池执行任务

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) {
            // worker实现了aqs,并且是不可重入的
            // 也就是说当worker加了锁就代表worker正在执行任务
            // 当线程池shutdown时只需要尝试使用worker获取锁trylock,如果成功了就代表worker没有运行
            // 如果失败了就代表worker运行着
            w.lock();
            // 当线程池的状态是stop,就中断线程,仅仅设置中断标记
            // 具体的中断还是看被中断的线程是怎么处理的
            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 {
        // 退出while循坏就会将worker   kill掉,具体的操作还要看getTask方法
        processWorkerExit(w, completedAbruptly);
    }
}

总结一下,为什么执行任务时worker需要加锁呢?

  • 加锁之后就代表了此worker正在运行,如果线程池在shutdown时,尝试获取worker的锁失败,证明此worker正在运行,就不会对worker进行interrupt;反之worker是空闲的,对空闲的worker进行interrupt

getTask方法----线程池获取任务队列和清除空闲线程

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

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

        // 当线程池是SHUTDOWN时并且队列为null,因为SHUTDOWN可以处理任务队列中的任务,所以只有
        // 任务队列为空时就返回一个null,runworker方法就会退出while循环,进入删除worker阶段
        // 当线程池是STOP往上时,也进入删除worker阶段
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();//提前减少worker的数量
            return null;
        }

        int wc = workerCountOf(c);

        // allowCoreThreadTimeOut代表核心线程是否支持超时,默认为false
        // 当线程池线程数wc>corePoolSize时,timed为true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        // 如果线程池线程数大于maximumPoolSize(什么时候会出现这种情况呢?),并且任务队列为空
        // 如果线程池线程数大于核心线程数,并且timedOut(当前线程已经等待了keepAliveTime的时间)
        // 如果此时任务队列没有任务,就需要将当前线程清除,返回null
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 如果线程池线程数大于核心线程数值,那就从任务队列中获取任务,当keepAliveTime时间
            // 内没有获取到任务,就设置timedOut = true,对应上方的代码
            // 如果线程池线程数小于核心线程数值,那就一直等待任务队列的任务,直到任务队列中
            // 新添加任务为止
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

总结一下,线程池什么时候会回收线程呢?

  1. 当线程池的状态为stop时
  2. 当线程池的状态为SHUTDOWN时并且任务队列没有任务
  3. 当线程池的线程数量大于核心线程数时并且当前线程在等待了keepAliveTime时间还没有获取到任务时

shutdown方法----中断空闲线程

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


private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

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();
    }
}

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 如果是运行的线程,return
        // 如果是大于等于TIDYING的状态,return
        // 如果是线程池状态是shutdown,但是任务队列不为空,return
        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 {
            // 设置状态
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 空实现
                    terminated();
                } finally {
                // 设置状态为TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}


总结一下,shutdown做了些什么

  • 将空闲线程interrupt
  • 设置线程池状态

shutdownNow方法

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        // 中断所有开始的worker
        interruptWorkers();
        // 将任务队列中的所有任务转移到tasks中
        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();
    }
}

void interruptIfStarted() {
    Thread t;
    // 如果worker已经start,并且线程没有中断标记
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

总结一下,shutdownNow做了些什么

  • 中断所有线程
  • 清除掉任务队列中的任务
  • 设置线程池状态

测试shutdown和shutdownNow方法

// 不会被中断的线程
class TestRunnableNoInterrupt implements Runnable {

    @Override
    public void run() {
        while(true) {
            System.out.println("我会一直运行,不被线程池中断");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("我不想中断");
            }
        }
    }
}
// 可以被中断的线程
class TestRunnableCanInterrupt implements Runnable{

    @Override
    public void run() {
        while (true) {
            System.out.println("我会被线程池中断");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
        }
        System.out.println("我被中断了");
    }
}
public class ThreadPoolTest {

    public static final ThreadPoolExecutor THREAD_POOL =
            new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));

    public static void main(String[] args) throws InterruptedException {
        THREAD_POOL.execute(new TestRunnableCanInterrupt());
        THREAD_POOL.execute(new TestRunnableNoInterrupt());
        Thread.sleep(500);
        THREAD_POOL.shutdown();
    }

}
  • main方法中使用的是shutdown方法中断线程,由于shutdown只会中断(interrupt方法)空闲的线程,所以上面两个线程会一直输出
  • 如果把shutdown改为shutdownNow方法,就会去中断所有的线程,不管这个线程是否在运行。上方的两个线程都调用了sleep,如果此时有线程调用他们的interrupt方法,sleep方法会抛出InterruptedException异常。线程池在shutdownNow的时候会暴力的调用所有线程的interrupt方法,所以这两个线程就会捕捉到异常,一个线程打印“我不想中断”,另外一个线程退出循环
  • 总结来说,就是shutdown和shutdownNow都不一定能把线程停止,这要看线程是怎么实现run方法的;
  • shutdown和shutdownNow的区别就是shutdown只会去调用空闲线程的interrupt方法,而shutdownNow不管当前线程是否在运行,直接调用interrupt方法,并且清空任务队列中的runnable对象,非常暴力

如何设置线程池中的线程数量

埋个坑