【J.U.C-executors】执行器框架—— ThreadPoolExecutor线程池实现原理分析

参考
Java多线程进阶(四十)—— J.U.C之executors框架:ThreadPoolExecutor
Java线程池实现原理及其在美团业务中的实践

【线程池简介】

1. 线程池概念

线程池的概念,是当有任务需要执行时,线程池会给该任务分配线程。如果当前没有可用线程,一般将任务放进一个队列中,直到有线程可用时,再将任务从队列中取出执行。
在这里插入图片描述

使用线程池的优势:

  • 当有任务需要执行时,线程池会给该任务分配线程,如果当前没有可用线程,一般会将任务放进一个队列中,当有线程可用时,再从队列中取出任务并执行
  • 减少系统因为频繁创建和销毁线程所带来的开销;
  • 自动管理线程,对使用方透明,使其可以专注于任务的构建。

2. 使用Demo

既然上文的三个执行器都是接口,那么我们要使用它们就需要创建它们的实现类。总体来说分为两种方式:

  • 通过 ThreadPoolExecutor 创建线程池;
  • 通过 Executors 创建线程池;

线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):

  1. ThreadPoolExecutor:最原始的创建线程池的方式,包含 7 个参数可供设置。
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  1. Executors.newFixedThreadPool:创建一个 固定大小 的线程池,可控制并发的线程数,超出的线程会在队列中等待;
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • corePoolSize与maximumPoolSize相同,表示线程池大小固定:只有核心线程,超出将会进入队列等待;
  1. Executors.newCachedThreadPool:创建一个 缓存 线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • corePoolSize为0,表示任务直接进入队列缓存;
  • maximumPoolSize为MAX_VALUE,表示允许创建的线程数无上限;
  • keepAliveTime回收时间为1min;
  1. Executors.newSingleThreadExecutor:创建 单个线程数 的线程池,它可以保证先进先出的执行顺序;
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • corePoolSize和maximumPoolSize都为1,表示线程池中最多只能有一个线程
  1. Executors.newScheduledThreadPool:创建一个可以 执行延迟任务 的线程池;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
  1. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
  1. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
 public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

可见通过Executors创建线程池,其本质也是通过调用ThreadPoolExecutor调整参数值实现的

在阿里编程规范中,线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。

Executors 返回的线程池对象的弊端如下:

  1. FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  2. CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

本文主要看ThreadPoolExecutor创建方式,使用Demo:

final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            5,
            10,
            1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
            //核心线程数:5
            //最大线程数:10
            //非核心线程超时时间:1min
            //等待队列的长度:无限制,Integer.MAX_VALUE

public static void main(String[] args) {
        for (int i = 0; i < 20; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(3000);
                        System.out.println("线程" + Thread.currentThread().getName() + "执行结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        System.out.println("pool execute over");
    }
            

【ThreadPoolExecutor类】

1. 继承关系

在这里插入图片描述

  • ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。
  • ExecutorService接口增加了一些能力:
    • 扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
    • 提供了管控线程池的方法,比如停止线程池的运行
  • AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
  • 最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

2. 构造函数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:核心线程数,线程池中始终存活的线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:最大线程数可以存活的时间。
    这个参数的含义可以理解为,当线程数超出了corePoolSize,那么多出的线程如果空闲的话,最长可以存活的时间,超时将被回收。即非核心线程的空闲存活时间;
    如果调用了allowCoreThreadTimeOut,那么当线程数没有超出corePoolSize,这个超时时间依然生效。
  • unit:线程的存活时间单位:
  • workQueue:一个阻塞队列,用来存储线程池等待执行的任务,(较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关)
  • threadFactory:线程工厂,主要用来创建线程。
  • handler:拒绝策略,(默认策略为 AbortPolicy):
    AbortPolicy:拒绝并抛出异常。
    CallerRunsPolicy:使用当前调用的线程来执行此任务。
    DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
    DiscardPolicy:忽略并抛弃当前任务。

概念:核心线程池、非核心线程池

ThreadPoolExecutor将线程池分为了两个逻辑上的部分:核心线程池和非核心线程池。

  • 核心线程池:大小为corePoolSize
  • 非核心线程池:大小为maximumPoolSize - corePoolSize

3. 内部变量

	private final BlockingQueue<Runnable> workQueue;
	
	private final ReentrantLock mainLock = new ReentrantLock();
	
	private final HashSet<Worker> workers = new HashSet<Worker>();

    private final Condition termination = mainLock.newCondition();
    
    private volatile int corePoolSize;

    private volatile int maximumPoolSize;

	private volatile ThreadFactory threadFactory;
  • workQueue:以阻塞队列保存的任务执行列表
  • workers:保存工作线程的集合
  • threadFactory:thread在调用构造方法时通过ThreadFactory来创建线程
  • corePoolSize:核心线程池大小
  • maximumPoolSize:最大线程池大小

4. 内部类:工作线程Worker

当我们向线程池提交一个任务时,将创建一个工作线程——Worker。其本质也就是实现了Runnable接口的线程,结构如下:

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** 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;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        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);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

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

        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) {
                }
            }
        }
    }

Worker是ThreadPoolExecutor的内部类,实现了AQS的框架。

【线程池的运行】

1. 运行机制

在这里插入图片描述

  • 线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。
  • 线程池的运行主要分成两部分:任务管理、线程管理
  • 任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:
    • (1)直接申请线程执行该任务;
    • (2)缓冲到队列中等待线程执行;
    • (3)拒绝该任务。
  • 线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

2. 线程池状态:生命周期

ThreadPoolExecutor中的变量:

	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

	// 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;
  • 线程池状态的保存——使用AtomicInteger类型的变量ctl
    • 低29位保存线程数
    • 高3位保存线程池状态
  • 线程池状态
    • running : 接受新任务, 且处理已经进入阻塞队列的任务
    • shutdown : 不接受新任务, 但处理已经进入阻塞队列的任务
    • stop : 不接受新任务, 且不处理已经进入阻塞队列的任务, 同时中断正在运行的任务
    • tidying : 所有任务都已终止, 工作线程数为0, 线程转化为TIDYING状态并准备调用terminated方法
    • terminated : terminated方法已经执行完成

线程池的生命周期:
在这里插入图片描述

【任务管理】

1. 任务提交——execute

我们实现了一个Runnable中的内容,会直接调用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();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
  • workerCount:线程池中的工作线程数
  • corePoolSize核心线程池上限
  • maximumPoolSize最大线程池上限
  1. case1:workerCount < corePoolSize :
    创建并启动一个新线程,执行新提交的任务;
  2. case2:workerCount >= corePoolSize,且阻塞队列未满
    插入任务至阻塞队列
  3. case3:maximumPoolSize > workerCount >= corePoolSize ,且阻塞队列已满
    创建并启动一个线程来执行新提交的任务
  4. case4:workerCount >= maximumPoolSize,且阻塞队列已满
    根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常
    在这里插入图片描述

2. Worker线程创建——addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                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 {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    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;
    }

参数:

  • firstTask
    • 如果指定了该参数, 表示将立即 创建一个新工作线程执行 该firstTask任务;
    • 否则复用已有的工作线程,从 工作队列中获取任务 并执行
    • (可以看到上述execute方法中,只有「workerCount >= corePoolSize,且阻塞队列未满」这一条件下,将任务插入阻塞队列,addWorker方法的firstTask参数才为null)
  • core [执行任务的工作线程归属于哪个线程池]:
    • true 核心线程池
    • false 非核心线程池

execute中的场景:

  • workerCount < corePoolSize,参数 [ firstTask:command、core:true ]
  • workerCount >= corePoolSize,且阻塞队列未满,参数 [ firstTask:null、core:false ]
  • workerCount < maximumPoolSize,参数 [ firstTask:command、core:false ]

流程:

  1. 检查线程池的状态
    • 线程池状态 = STOP / TIDYING / TERMINATED:不再接受任务,直接返回
    • 线程池状态 = SHUTDOWN / STOP / TIDYING / TERMINATED 且 firstTask != null:
      因为当线程池状态≥ SHUTDOWN时, 不再接受新任务的提交,所以直接返回
    • 线程池状态 = SHUTDOWN / STOP / TIDYING / TERMINATED 且 阻塞队列为空:
      队列中已经没有任务了, 所以也就不需要执行任何任务了,可以直接返回
  2. 判断工作线程数是否超限
    • 工作线程数超过最大工作线程数(2^29-1)
    • 工作线程数超过核心线程池上限(入参core为true, 表示归属核心线程池)
    • 工作线程数超过总线程池上限(入参core为false, 表示归属非核心线程池)
  3. 条件都符合,workers.add(),将传入的firstTask包装为Worker类,然后加入到保存工作线程的HashSet中。
    在这里插入图片描述

3. 线程的获取及运行——runWorker方法

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 pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                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);
        }
    }
  1. 获取firstTask,如果为null,则在while循环中通过getTask获取;
  2. 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。
  3. 执行任务
  4. 如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程
    在这里插入图片描述

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;
            }
        }
    }
  1. for循环尝试获取线程
  2. 如果线程池状态为shutdown,或者为stop且workQueue为空,那么返回null
  3. 获取线程池当前线程数wc,判断线程数是否过多,如果是返回null
  4. 通过workQueue.poll或workQueue.take限时或阻塞地获取任务,返回
    在这里插入图片描述

4.任务拒绝——reject

final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

handler为RejectedExecutionHandler接口,有四个实现类
在这里插入图片描述
各个拒绝策略的描述如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值