ThreadPoolExecutor源码走读

工作中经常用到线程池,最常见的写法是Executors.newFixedThreadPool,线程池具体是如何处理我们提交的任务的呢?这里再走读源码记录一遍。

线程池这块的整体结构:

从整体上看类图,具体线程池具体实现其实就2个类ThreadPoolExecutor和ScheduledThreadPoolExecutor。

经常使用到的Executors类中可以看到它提供静态方法创建各种ThreadPoolExecutor以及ScheduledThreadPoolExecutor。
例如,创建固定线程池:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

创建单个线程的线程池:

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
}

创建具有延时执行功能的线程池:

public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

其实无非就是包装了创建线程池对象的方法,返回对应的实例,其实是工厂模式。
具体threadFactory、LinkedBlockingQueue、ThreadPoolExecutor、ScheduledThreadPoolExecutor类干了什么后面再继续看。

1. ThreadPoolExecutor

先看ThreadPoolExecutor类,它继承了AbstractExecutorService,我们知道一般抽象类其实是统一实现了一部分通用的接口功能,避免各子类重复实现,从这一点分析,也可以看出来上面的整体类图是有遗漏的,不太可能只有一个ThreadPoolExecutor继承它,应该还有其它子类,继续分析它的子类,会发现在Executors在创建线程池时,有静态内部类继承了AbstractExecutorService:

static class DelegatedExecutorService extends AbstractExecutorService {
        private final ExecutorService e;
        DelegatedExecutorService(ExecutorService executor) { e = executor; }
        public void execute(Runnable command) { e.execute(command); }
        public void shutdown() { e.shutdown(); }
        public List<Runnable> shutdownNow() { return e.shutdownNow(); }
        public boolean isShutdown() { return e.isShutdown(); }
        public boolean isTerminated() { return e.isTerminated(); }
        public boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException {
            return e.awaitTermination(timeout, unit);
        }
        public Future<?> submit(Runnable task) {
            return e.submit(task);
        }
        public <T> Future<T> submit(Callable<T> task) {
            return e.submit(task);
        }
        ...

这个方法包装了AbstractExecutorService,没有设置corePoolSize、keepAliveTime等方法,这个包装类用来创建单线程池

(newSingleThreadExecutor/newSingleThreadScheduledExecutor)等不可设置这些属性的实例。接下来继续看ThreadPoolExecutor源码属性


,它有几个状态值:

static final int RUNNING    = 0;
static final int SHUTDOWN   = 1;
static final int STOP       = 2;
static final int TERMINATED = 3;

线程池状态之间的切换:

其它属性:

private final BlockingQueue<Runnable> workQueue;
private final HashSet<Worker> workers = new HashSet<Worker>();
private final ReentrantLock mainLock = new ReentrantLock();
private final Condition termination = mainLock.newCondition();
private volatile long  keepAliveTime;
private volatile boolean allowCoreThreadTimeOut;
private volatile int   corePoolSize;
private volatile int   maximumPoolSize;
private volatile int   poolSize;
...

workQueue用于存储可执行的任务,是一个阻塞队列类型BlockingQueue。
workers保存了所有线程池中工作的线程。
mainLock锁用来控制poolSize、corePoolSize、maximumPoolSize、runState、workers集合。由此可见多线程环境下,对共享变量的所有访问都需要同步化。
keepAliveTime如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
allowCoreThreadTimeOut 超时策略应用于核心线程。
corePoolSize核心线程数
maximumPoolSize允许的最大线程数。
poolSize池中的当前线程数。

其内部方法有很多,那就先从我们最常用的方法开始看起,串成一条执行链路。

newFixedThreadPool.execute(new ThreadForpools(index));

根据代码的执行逻辑,大概提交一个任务的执行流程如下:

看源码,你会发现都使用到了充入锁ReentrantLock:

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
   Thread t = null;
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       if (poolSize < corePoolSize && runState == RUNNING)
           t = addThread(firstTask);
   } finally {
       mainLock.unlock();
   }
   return t != null;
}

mainLock 是全局属性,一个线程池实例共用一个锁。

当任务添加到worders后,又是怎么执行的呢?

worker实际上是线程池自己定义的一个内部类 java.util.concurrent.ThreadPoolExecutor.Worker:

当提交一个任务到workers中后,调用了线程的start方法,实际上执行了,Worker类的run方法:

 public void run() {
            try {
                hasRun = true;
                Runnable task = firstTask;
                firstTask = null;
                while (task != null || (task = getTask()) != null) {
                    runTask(task);
                    task = null;
                }
            } finally {
                workerDone(this);
            }
 }

启动一个任务后,该线程会不停的调用getTask方法获取任务调用runTask执行:

Runnable getTask() {
        for (;;) {
            try {
                int state = runState;
                if (state > SHUTDOWN)
                    return null;
                Runnable r;
                if (state == SHUTDOWN)  // Help drain queue
                    r = workQueue.poll();
                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
                else
                    r = workQueue.take();
                if (r != null)
                    return r;
                if (workerCanExit()) {
                    if (runState >= SHUTDOWN) // Wake up others
                        interruptIdleWorkers();
                    return null;
                }
                // Else retry
            } catch (InterruptedException ie) {
                // On interruption, re-check runState
            }
        }
}

getTask会判断线程池状态:

  • 1.如果是STOP或者TERMINATED,则返回task=null,调用workers.remove(w) 从workers中移除。

  • 2.如果是SHUTDOWN,则获取任务队列workQueue头部的任务(workQueue.poll())

  • 3.如果是poolSize > corePoolSize || allowCoreThreadTimeOut,则返回workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),这里就体现了keepAliveTime的用法,如果allowCoreThreadTimeOut设置为true,则空闲的多余corePoolSize的线程将会在存活keepAliveTime时长后终止。

  • 4.如果是其他情况,则会调用workQueue.take() 一直阻塞,直到有新的任务添加到workQueue。

当获取到task!=null时,继续调用runTask执行,如果线程池的状态是STOP|TERMINATED,或者当前任务状态是hasRun但是被中断了,则中断当前任务线程,否则调用run方法,这里需要注意了,start和run方法的区别,直接调用run方法是不会创建新线程的,只是同步的run方法调用,当该任务线程执行完run方法后,又会堵塞到workQueue.take方法上,所以能够保证线程池的线程一直存活。

再看下,线程池的状态是如何切换的

runState因为是int,所以初始是0(RUNNING),有如下的方法会改变线程池的状态:

public void shutdown()
public List shutdownNow()

1.调用shutDown方法,无返回。

  • 置线程池状态为SHUTDOWN,中断所有workers空闲线程。这是判断是否线程空闲,用了个很巧妙的办法:

  • 在执行runTask时,都会获取runLock锁,然后在判断是否空闲时,参数下获取该锁tryLock:

如果没有获取到,说明该线程是任务执行状态,如果获取到则interrupt。

  • 调用tryTerminate如果poolSize=0,workQueue也为空,则把线程池状态置为TERMINATED。

2.调用shutDownNow方法,有返回

  • 置线程池状态为STOP,立即调用workers中所有执行线程的thread.interrupt方法
  • copy workQueue中任务
  • 调用tryTerminate 如果poolSize=0,workQueue也为空,则把线程池状态置为TERMINATED。

可以看出来这二个方法的区别是:
shutDownNow 直接调用thread.interrupt,不会等正在执行中的任务线程执行结束,返回任务队列中的任务。
shutDown 等待执行中的线程执行结束,没有返回值。

2.BlockingQueue

队列是作为线程池暂存提交任务的地方,先来看下队列这块的整体结构:

从整体上看ConcurrentLinkedQueue的功能相对较少,只具备Queue接口、abstractQueue类的功能。其它6类在此之上还具备blockingQueue或/blockingDeque接口的功能。

先看初始化线程池时,最常用的LinkedBlockingQueue。

LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE(231-1)的容量 。它的内部实现是一个链表。

上面走读线程池源码的时候说到,如果当前线程数大于corePoolSize并且线程池状态是RUNNING,就会添加到阻塞任务队列中,调用了workQueue.offer(command)。

public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        //count表示当前队列中元素个数,AtomicInteger类型确保并发条件下的更新操作是原子的。
        final AtomicInteger count = this.count;
        //如果队列满了,直接返回false
        if (count.get() == capacity)
            return false;
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        //如果队列没有满,则获取该队列putLock锁
        putLock.lock();
        try {
            if (count.get() < capacity) {
                enqueue(e);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    //如果放入后,队列没有满,则唤醒notFull条件等待线程。
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }
    ```

当worker在执行时,线程池状态state == SHUTDOWN 或者 poolSize > corePoolSize 或者allowCoreThreadTimeOut有设置时, 线程池获取任务队列都会调用poll() : 

```java
public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                //获取队列首项,并从队列中移除
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    //移除后,队列非空,则唤醒notEmpty条件等待线程
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    
    //获取队列首项,并从队列中移除
    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }
    ```
可以看出调用poll方法时,如果获取到锁,则不会堵塞,队列为空则直接返回null。

所以线程池为 SHUTDOWN状态时,不会堵塞任务队列的获取,能速度结束线程执行。

但是如果线程池状态为RUNNING切poolSize<=corePoolSize时,会调用阻塞队列的workQueue.take()获取任务:

```java
public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //获取锁时,可中断等待
        takeLock.lockInterruptibly();
        try {   
                // 防止虚假唤醒,所以用while
                while (count.get() == 0) {
                    //队列为空时,等待notEmpty条件唤醒
                    notEmpty.await();
                }
            //否则返回队首任务,并从队列中移除。
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

可见看出,当队列任务为空时,take方法会堵塞当前任务线程,同时等待队列任务添加,这样就能保持当前线程不会结束从而被回收。

ThreadPoolExecutor是Java中用于管理线程池的一个类。它是Executor框架的一种具体实现,提供了对线程池的管理和控制。 以下是ThreadPoolExecutor的简化版源码: ```java public class ThreadPoolExecutor { private final BlockingQueue<Runnable> workQueue; // 任务队列 private final HashSet<Worker> workers; // 工作线程集合 private final ReentrantLock mainLock = new ReentrantLock(); // 控制线程池状态的锁 private final Condition termination = mainLock.newCondition(); // 线程池终止条件 private volatile boolean isShutdown = false; // 线程池是否已关闭 private volatile boolean isTerminating = false; // 线程池是否正在关闭 private volatile boolean isTerminated = false; // 线程池是否已终止 private int corePoolSize; // 核心线程数 private int maximumPoolSize; // 最大线程数 private long keepAliveTime; // 非核心线程的空闲超时时间 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.keepAliveTime = unit.toNanos(keepAliveTime); this.workQueue = workQueue; this.workers = new HashSet<>(); } public void execute(Runnable task) { if (task == null) { throw new NullPointerException(); } if (isShutdown) { throw new RejectedExecutionException(); } int workerCount = workers.size(); if (workerCount < corePoolSize) { // 如果核心线程数未满,直接创建并启动一个核心线程来执行任务 addWorker(task, true); } else if (workQueue.offer(task)) { // 将任务添加到任务队列中 // do nothing } else if (workerCount < maximumPoolSize) { // 如果任务队列已满但线程数未达到最大值,则创建并启动一个非核心线程来执行任务 addWorker(task, false); } else { reject(task); // 否则拒绝执行任务 } } private void addWorker(Runnable task, boolean core) { Worker worker = new Worker(task); worker.thread.start(); // 启动工作线程 workers.add(worker); if (core) { corePoolSize++; } } private void reject(Runnable task) { throw new RejectedExecutionException(); } public void shutdown() { mainLock.lock(); try { isShutdown = true; interruptIdleWorkers(); } finally { mainLock.unlock(); } } private void interruptIdleWorkers() { for (Worker worker : workers) { if (!worker.thread.isInterrupted() && worker.tryLock()) { try { worker.thread.interrupt();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值