ThreadPoolExecutor&ScheduledThreadPoolExecutor线程池原理详解

·  阅读 1606

概述

线程池是什么不用多说,我们先来看一下各种线程池继承关系,鸟瞰整个全貌

image.png 先看看顶层接口

public interface Executor {
    # 只有一个执行方式
    void execute(Runnable command);
}
复制代码

ExecutorService 在 Executor 基础上增加了一个 submit方法,可以用于传入 Callable接口的数据

public interface ExecutorService extends Executor {

    <T> Future<T> submit(Callable<T> task);
}
复制代码

接下来我们看看实现类的源码逻辑

AbstractExecutorService

public abstract class AbstractExecutorService implements ExecutorService {

    # 将 callable包装成futuretask
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
    # 包装成futuretask然后交给子类执行
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
}
复制代码

ThreadPoolExecutor

ThreadPoolExecutor 最核心的思想就是一堆worker在阻塞队列里面拉任务执行,就这么简单 image.png 我们来看看源码,首先看看 内部类 Worker

# worker 继承与 AQS,是一把独占锁,并且实现了 Runnable,可以被放入线程中执行
private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{

    # 运行当前worker的线程
    final Thread thread;
    # 初始化执行的任务
    Runnable firstTask;
    # 完成的任务数
    volatile long completedTasks;

    # 创建worker
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        # 这一行很重要,将自身 runnable 和 thread绑定在了一起
        this.thread = getThreadFactory().newThread(this);
    }

    # 当 thread 启动的时候 这个run方法就会开启
    public void run() {
        runWorker(this);
    }

    # AQS 的获取释放资源,可以看出是独占锁
    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;
    }
}
复制代码

看的出 Worker 是一个 AQS的锁,并且还是一个runnable,自身绑定了一个线程,当线程开启的时候,自己就会执行 run方法。那么线程什么时候开启呢,继续往下看源码. 先看几个关键的属性,ctl ThreadPoolExecutor 用了 int 32 位中前3位来表示线程池的状态,后面29位表示线程池的worker数量。 判断的时候都是用位运算来操作

public class ThreadPoolExecutor extends AbstractExecutorService {

    # ThreadPoolExecutor 用了 int 32 位中前3位来表示线程池的状态,后面29位表示线程池的worker数量
    # 判断的时候都是用位运算来操作
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }




    # 新建线程池传入的阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    # Worker集合
    private final HashSet<Worker> workers = new HashSet<Worker>();
复制代码

然后这就是经典的线程池提交任务逻辑. 核心线程数没满创建线程,满了放队列,队列满了创建最大线程数,再满了走拒绝策略

    # 线程池提交任务方法
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        # 获得状态值
        int c = ctl.get();
        # 通过位运算算出来当前worker数量是不是小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
            # 如果是直接创建worker线程返回
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        # 判断当前线程池是不是还在运行,是的话尝试添加到阻塞队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            # 再次判断状态,如果不运行了就踢出任务走拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            # 如果当前没有工作线程则创建一个worker来执行任务,如果核心线程数设置为0可能走这里
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        # 队列添加失败走最大线程数逻辑
        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);
        # 判断线程池状态,如果不符合就返回false
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            # 拿到当前线程数
            int wc = workerCountOf(c);
            # 如果当前线程数已经超过核心或者最大线程数了,就创建worker失败
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            # 可以继续创建线程,把状态加一
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            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 {
        # 创建worker对象,记住worker是一个锁,还是一个runnable,里面有一个thread,执行了start方法就是执行worker里面的run方法
        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)) {
                    # 如果worker里面的线程被别人start了抛出异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    # 线程池中添加worker
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            # 如果worker放入线程池当中了,开启worker里面的线程,worker就会执行run方法
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
复制代码

可以看到,方法和核心逻辑就是 创建了一个Worker对象放到线程池中,然后开启一个线程执行Worker里面的方法. 所以接下来就看看Worker里面的run方法到底在做些什么.

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        # 别的不用管,就看这一句,task = 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这个runnable 的run方法
                    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);
    }
}
#  从阻塞队列中获取任务
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        # 又是一堆状态判断不用看
        int c = ctl.get();
        int rs = runStateOf(c);

        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;
        }
    }
}
复制代码

至此整个 ThreadPoolExecutor的逻辑就清楚了,一个worker当被添加到线程池中的时候会开启一个线程,那个线程就等着从 阻塞队列中take数据,获得到任务之后就执行,执行完毕继续在队列中等待。

ScheduledThreadPoolExecutor

image.png 先来看看基本使用, ScheduledThreadPoolExecutor 分为可以延迟运行一次的任务,和定时执行的任务

public static void main(String[] args) throws Exception {

    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
    # 定时执行任务
    executor.scheduleAtFixedRate(()->{
        System.out.println("haha");
    },0,2,TimeUnit.SECONDS);

    # 只执行一次的任务
    executor.schedule(()->{
        System.out.println("fsfsf");
    },5,TimeUnit.SECONDS);

    System.in.read();
}
复制代码

用法很简单,需要注意的是,任务中的延时时间是根据开始任务的时间来算的,所以加入延时2秒执行,任务执行了3秒,那么执行完毕后,因为已经超过2秒了,就会马上执行任务,而不会再等待2秒。 先来看看提交任务的代码, 其实就是创建一个 ScheduledFutureTask 丢到队列中等待消费

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0)
        throw new IllegalArgumentException();
        # 把任务包装成了一个 ScheduledFutureTask
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period));
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    # 将任务放到队列中
    delayedExecute(t);
    return t;
}
# 把任务放入队列
private void delayedExecute(RunnableScheduledFuture<?> task) {
    if (isShutdown())
        reject(task);
    else {
        # 找到队列,放进去
        super.getQueue().add(task);
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            task.cancel(false);
        else
            ensurePrestart();
    }
}
复制代码

有了 ThreadPoolExecutor 的前置知识,我们知道 worker会不断的监听队列里面的任务,然后拿下来执行任务。那么接下来我们就应该看看这个 ScheduledFutureTask 中的 run方法里面逻辑

private class ScheduledFutureTask<V>
        extends FutureTask<V> implements RunnableScheduledFuture<V> {

    public void run() {
        # 判断是不是定时执行,在初始化的时候设置
        boolean periodic = isPeriodic();
        if (!canRunInCurrentRunState(periodic))
            cancel(false);
        else if (!periodic)
            # 如果不是定时执行,执行一次就结束了
            ScheduledFutureTask.super.run();
        else  {
            # 执行定时任务,这里的执行不会修改 FutureTask的状态
            if(ScheduledFutureTask.super.runAndReset()){
                # 设置下次执行的时间
                setNextRunTime();
                # 重新把任务放到队列里面去
                reExecutePeriodic(outerTask);
            }
            
        }
    }
    void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            # 找到队列把任务重新丢进去
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

}
复制代码

核心思想就是worker执行完任务之后重新丢回队列的这么一个逻辑

image.png

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改