Java线程池ThreadPoolExecutor运行机制和源码解析

线程池简介

线程的每次创建和销毁都会产生的一定的系统资源和时间的开销。正如几乎所有重资源都使用池化技术(数据库连接池、redis连接池等)进行管理,线程作为操作系统宝贵的资源,对它的使用需要进行控制管理,线程池就是使用了池化的技术对线程进行复用和管理。使用线程池对比单独启动线程有以下好处:

  1. 快速响应用户请求

线程的启动需要一定时间开销,而使用了池化的线程时,当任务到达,节省了这部分的时间。Tomcat就使用自行扩展的ThreadPoolExecutor处理http请求,减小响应时间。

  1. 减少资源开销

对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
对资源无限申请缺少抑制手段,将引发系统资源耗尽的风险。
系统无法合理管理内部的资源分布,将降低系统的稳定性。

  1. 提高资源管理性

使用线程池可对线程资源进行统一的命名、创建、销毁、监控和分配任务,还能通过信号量来对线程同时执行任务的数量进行限制。

jdk里最基本的线程池的实现类是ThreadPoolExecutor,它是Java管理线程池的一个重要类,本文正是对这个类的讲解。

线程池运行机制

在这里插入图片描述

线程池的总体运行机制如上图(网上找的一个图),内部管理了一批线程和一个缓冲阻塞队列,通过execute()方法向其提交任务,之后线程池会调度分配任务,根据不同条件时会产生不同的策略:

  1. 直接新建一个线程运行该任务
  2. 将任务放进队列,线程池的线程从队列中取任务去执行
  3. 直接新建一个线程运行该任务直至达到最大线程数(和1中的条件有差异)
  4. 拒绝任务,执行拒绝策略

这里对概念和机制作简单介绍,下面将作详细讲解。

类图

image

ThreadPoolExecutor类实现了ExecutorService接口,下面是主要方法的简要说明:

  • execute(Runnable command):提交一个任务给线程池执行。
  • shutdown():优雅地关闭线程池,不再接受新的任务,等待已经提交的任务执行完成后关闭线程池。
  • shutdownNow():立即关闭线程池,并尝试中断所有执行中的任务。

线程池状态

线程池的状态和数量存储在Integer原子类的实例ctl里,高3位为线程池状态,低29位为线程池的工作线程数。

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 static final int CAPACITY = (1 << COUNT_BITS) - 1; 这段代码得到值转为二进制是
00011111 11111111 11111111 11111111

~CAPACITY得到值转为二进制是
1110000 00000000 00000000 00000000

所以
private static int runStateOf(int c) { return c & ~CAPACITY; } 得到的是c的高3位的值
private static int workerCountOf(int c) { return c & CAPACITY; } 得到的是c的低29位的值
private static int ctlOf(int rs, int wc) { return rs | wc; } 由于rs的低29位为0、wc的高3位为0,所以这个方法是将rs的高3位和wc的低29位的值组合起来成为32位的int值

线程池包含了五种状态:

状态备注
RUNNING能接受新提交的任务,也能处理阻塞队列中的任务
SHUTDOWN不再接受新任务,但能继续处理阻塞队列。在调用shutdown()方法中转为此状态
STOP不再接受新任务,不再处理阻塞队列,中断正在运行的线程。在调用shutdownNow()方法中转为此状态
TIDYING所有任务已终止,worker数量为0
TERMINATED当调用terminated()后转为该状态

线程池的状态由内部维护,随着生命周期的转变而变化,其状态的流转图如下:

在这里插入图片描述

核心参数

线程池的构造函数:

    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

构造函数的参数详细解释:

参数类型名称备注
corePoolSizeint核心线程数除非设置了allowCoreThreadTimeOut,不然即使空闲也不会回收
maximumPoolSizeint最大线程数线程池允许的最大线程数量
keepAliveTimelong线程存活的时间当完成当次任务后线程存活的时间
workQueueBlockingQueue阻塞队列可自行选择实现类
threadFactoryThreadFactory线程工厂可设置线程属性
handlerRejectedExecutionHandler拒绝策略当线程容量溢出时执行的策略

任务调度流程

线程池提交任务的入口是execute()方法,在该方法内部决定了提交任务的分配和调度过程,如下图所示:

在这里插入图片描述

主要调度逻辑:

  1. 当前线程数小于corePoolSize时,每次创建一个新线程执行任务
  2. 当前线程数大于corePoolSize并且workQueue未满,把任务放进workQueue
  3. 当前线程数处于corePoolSize和maximumPoolSize之间并且workQueue已满,创建新线程执行任务
  4. 当前线程数大于等于maximumPoolSize,执行拒绝策略
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) //当前线程数小于corePoolSize时
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        //当前线程数大于corePoolSize并且workQueue未满,把任务放进workQueue
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //当前线程数处于corePoolSize和maximumPoolSize之间并且workQueue已满,创建新线程执行任务
    else if (!addWorker(command, false)) 
        reject(command);  //当前线程数大于等于maximumPoolSize,执行拒绝策略
}

Worker类

Worker是线程池的核心内部类,存储着线程,实现了Runnable接口,通过继承同步器AbstractQueuedSynchronizer简单的实现了锁,这个锁用于防止在关闭线程池时正在运行任务的线程被interrupt()方法中断。

/**
 * ThreadPoolExecutor的内部类Worker
 */
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable {

        final Thread thread;  //线程池的线程

        Runnable firstTask;

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //新建线程
            this.thread = getThreadFactory().newThread(this);
        }

        /** 任务执行的核心方法 */
        public void run() {
            runWorker(this);
        }
        
        //省略代码……

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

    }

线程池通过addWorker(Runnable firstTask, boolean core)方法新建Worker实例,在其构造函数会新建一个线程,创建成功后启动这个线程。线程池对线程的管理就是通过对Worker的管理实现的。

//ThreadPoolExecutor通过内部变量workers来管理线程
private final HashSet<Worker> workers = new HashSet<Worker>();

private boolean addWorker(Runnable firstTask, boolean core) {
        Worker w = null;
        try {
            //省略代码……
            w = new Worker(firstTask); //新建worker实例
            final Thread t = w.thread;
            if (t != null) {
                try {
                    //省略代码……
                    workers.add(w);
                }
                if (workerAdded) {
                    t.start(); //启动worker线程
                }
            }
            //省略代码……
        }
        return workerStarted;
    }
}

线程运行的核心方法

worker线程启动后执行的run()就是runWorker(Worker w),是线程执行任务的核心方法,内部是一个循环,运行的逻辑如下:

  1. 当是新建的线程时, 会运行Worker实例初始化时的firstTask任务。
  2. firstTask任务完成时,调用getTask()从阻塞队列方法取得下一个任务。
  3. 当调用getTask()方法返回null时会退出循环,执行processWorkerExit()方法退出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 {
            /**
            * 循环从队列里取任务,当task=getTask()方法返回null时会退出循环
            */
            while (task != null || (task = getTask()) != null) {
                //加锁防止shutdown方法会interrupt正在运行的任务
                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.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //若上面的主循环结束,回收线程
            processWorkerExit(w, completedAbruptly); 
        }
    }

拒绝策略

使用了有界等待队列的线程池对任务的总容量作了限制,当线程池的任务总容量达到了maximumPoolSize和等待队列容量之和,线程池不会再接收任务,而是执行拒绝策略。java原生有四种拒绝策略,另外可以实现java.util.concurrent.RejectedExecutionHandler接口进行扩展。

效果
AbortPolicy抛出异常(默认的拒绝策略)
CallerRunsPolicy在当前线程运行任务
DiscardOldestPolicy丢弃队列最旧的任务
DiscardPolicy丢弃当前的任务

阻塞队列

在达到核心线程之后,提交的任务会放在阻塞队列,每个工作线程在完成本次任务后从阻塞队列取任务执行,阻塞队列的核心方法为:

  • boolean offer(E e),将元素放入队列。
  • E take(),从队列取元素,如果队列没有元素,当前线程一直阻塞直至其它线程增加队列元素后被唤醒。
  • E poll(long timeout, TimeUnit unit),从队列取元素,如果队列没有元素,当前线程一直阻塞timeout时长直至其它线程增加队列元素后被唤醒,当等待超时的时候返回null。
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {
    boolean offer(E e);
    E take() throws InterruptedException;
    E poll(long timeout, TimeUnit unit) throws InterruptedException;
    //省略代码...
}

JDK实现了几个阻塞队列,可以根据不同的需要使用合适的阻塞队列,汇总如下:

有界队列说明
ArrayBlockingQueue数组阻塞队列,采用数组实现,需要设置容量,插入元素时若超过容量则返回false。
LinkedBlockingQueue链表阻塞队列,先进先出。
DelayQueue延迟阻塞队列,当元素的值小于0时出列(延迟到期),元素存储在内部包含的PriorityQueue。
PriorityBlockingQueue优先阻塞队列,初始化时传入Comparator的实现类用于排序。
SynchronousQueue-同步队列,是不存储元素的,是线程和线程之间直接传递,每线程一个插入的操作必须等待另一个线程的移除操作。

任务在线程池的执行流程

线程池内部的主要组件和实现在上面已经进了介绍,通过下面的时序图展示了一个任务在线程池中执行的完整流程:

在这里插入图片描述

任务从提交开始在线程池执行过程的关键代码如下:

public class ThreadPoolExecutor {

	//工作线程类, 初始化的时候会创建线程,线程的运行的Runnable为this(当前worker实例)
    private final class Worker implements Runnable {
        final Thread thread;
        Runnable firstTask;
        
       Worker(Runnable firstTask) {
            this.firstTask = firstTask;
            //创建线程, 线程的运行的Runnable为this(当前worker实例)
            this.thread = getThreadFactory().newThread(this);
        }
        
        //线程运行的run方法
        public void run() {
            runWorker(this);  
        }
    }


   //1. 提交任务,由线程池进行任务调度
   public void execute(Runnable command) {
        //省略代码……
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))              //调用步骤2的方法
                return;
        }
        if (isRunning(c) && workQueue.offer(command)) { //任务进入队列
            //省略代码……
        }
        else if (!addWorker(command, false))
            reject(command);                            //拒绝任务
        //省略代码……
    }
    
    //2. 增加并启动worker工作线程
    private boolean addWorker(Runnable firstTask, boolean core) {
        Worker w = null;
        try {
            //省略代码……
            w = new Worker(firstTask); //2.1 新建worker实例
            final Thread t = w.thread;
            if (t != null) {
                try {
                    //省略代码……
                    workers.add(w);   //2.2 放入集合用于管理
                }
                if (workerAdded) {
              		//2.3 启动worker线程,
              		//    线程启动后运行worker的run()方法,
              		//    worker的run()会运行runWorker(this)方法,
              		//    所以线程启动后实际运行的是runWorker(this)方法;
                    t.start();        
                }
            }
            //省略代码……
        }
        return workerStarted;
    }
    
    //3. 线程运行循环体
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        //省略代码……
        try {
            //核心循环体: 执行第一个任务,完成后循环地从队列取任务执行
            while (task != null || (task = getTask()) != null) {//通过getTask()取任务
                //省略代码……
                try {
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    }
                    //省略代码……
                }
                //省略代码……
            }
        }
    }
}

线程池的关闭

线程池的核心线程是通过shutdown()shutdownNow()方法进行关闭的,这两个方法分别会把线程池的状态设置为SHUTDOWNSTOP,这时候线程池不会再接受新的任务,通过使用worker的tryLock()方法尝试获得锁能否成功判断是否空闲,能取得锁就是空闲的线程,然后去中断空闲的线程,这样就保证不会中断正在运行的任务的线程。

shutdown()shutdownNow()两个方法的区别是shutdownNow()会把等待队列清空。

以下是shutdown()方法的代码:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN); //线程池状态设置为SHUTDOWN
            interruptIdleWorkers();    //中断空闲线程,回收资源
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }


    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //尝试获取worker的锁
                if (!t.isInterrupted() && w.tryLock()) {  
                    try {
                        t.interrupt();  //对worker工作线程进行中断
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

    

线程池空闲worker线程的关闭回收

要搞明白空闲线程的关闭回收需要理解线程中断的一些知识,下面就来讲一下。

线程中断知识点回顾开始

——————— 知识点回顾开始 ———————

java有两种方法通知线程结束线程

1、调用线程的stop()方法(已废弃)

直接退出线程,因为太暴力会产生不可知的结果该方法已废弃

2、调用线程的中断方法

线程的中断方法是interrupt(),首先要明确的一点是调用interrupt()方法后线程并不是马上关闭不是马上关闭不是马上关闭!!

而是根据不同情况出现以下两种不同结果:

  • (1)、当使用interrupt()方法去打断处于阻塞状态的线程时,会抛出InterruptedException异常,而不会更新打断标记,因此,虽然被打断,但是打断标记依然为false,使用Thread类的isInterrupted()方法可返回打断标记。

线程阻塞的情况有以下这些:

 * @see     java.lang.Object#wait()
 * @see     java.lang.Object#wait(long)
 * @see     java.lang.Object#wait(long, int)
 * @see     java.lang.Thread#sleep(long)
 * @see     java.lang.Thread#sleep(long)
 * @see     java.util.concurrent.locks.Condition.await
  • (2)、当使用interrupt()方法去打断非阻塞线程时,被打断的线程会继续运行,但是该线程的打断标记会更新,更新为true,因此可以根据打断标记来作为判断条件使得线程停止。线程是否打断的方法为isInterrupted()

所以,调用线程的interrupt()方法并不会停止和关闭线程,程序自行根据打断标记或InterruptedException异常自行结束线程的运行

——————— 知识点回顾结束 ———————

下面重新回到线程池空闲worker线程的关闭回收的主题。

当线程池处于SHUTDOWN状态时,不再接受新的任务,意味着只要正在运行的任务和队列里的任务全部运行完毕,线程池里所有任务就完成了。

上面介绍了线程池的关闭的方法,接下来思考一个问题,既然线程的interrupt方法是相当于一种通知机制,为什么通过调用空闲worker线程的interrupt()方法能关闭回收线程?

其中空闲的worker线程的锁已释放,正在从队列取数据处于阻塞状态等待任务入列。

当调用线程池的shutdown()方法时,会遍历worker尝试获得锁,

取得空闲的worker的锁并去interrupt线程,此时阻塞队列抛出InterruptedException,在getTask()方法内捕获异常后结束本次循环,运行下一次循环时由于线程池状态已变为大于等于SHUTDOWNgetTask()返回null,runWorker()的作为线程运行任务的核心循环体也结束了,最后调用processWorkerExit回收worker实例,在processWorkerExit方法结束后,线程的run()方法就完成了运行,线程随之结束。

结合下面的时序图和具体代码的流转作深一步理解

空闲worker线程关闭退出的时序图

在这里插入图片描述

空闲worker线程被中断后关闭退出的逻辑代码

在这里插入图片描述
在代码标红步骤 3.若await阻塞时被interrupt抛出异常InterruptedException的说明:

take()方法中执行到lock.lockInterruptibly()(等待锁)或 notEmpty.await()处的线程为空闲线程,会被interrupt从而抛出InterruptedException异常是因为此时线程池调用了shutdown()shutdownNow()

awaitTermination()方法分析

线程池的awaitTermination()方法从字面意思是等待线程池终止,亦即等待线程池状态变为TERMINATED
线程池的awaitTermination()通常是和shutdown()shutdownNow()搭配使用的,在线程池调用了上面的两个关闭方法中的任一个后调用awaitTermination()阻塞主线程直至线程池所有任务完成并且它的状态为TERMINATED

有两种情况会导致该方法没有等到线程池状态变为 TERMINATED 而提前结束:

  1. 等待超时
  2. 主线程被interrupt()

源码:

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos); //等待有线程执行terminated()后唤醒它
        }
    } finally {
        mainLock.unlock();
    }
}

//每个worker退出后执行该代码
final void tryTerminate() {
    for (;;) {
        //省略代码……
        int c = ctl.get();
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                //状态TIDYING代表线程池所有任务已完成
                //调用terminated()方法后设置线程池状态为TERMINATED
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
    }
}

什么时候调用线程池的关闭方法后要用awaitTermination()方法?

如果主线程需等待线程池任务全部完成后再作往下操作,就需要调用awaitTermination()。若主线程不需要在线程池终止后才能做下一步则不需要调用awaitTermination(),因为线程池的线程工厂创建的线程默认为非守护线程,线程池的任务会运行直至完成。

以下是一个使用例子:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize, 
    maximumPoolSize, 
    keepAliveTime, 
    TimeUnit.SECONDS, 
    new LinkedBlockingQueue<>());

// 执行一些任务...
executor.execute(task);

executor.shutdown(); // 停止接受新任务并尝试关闭

try {
    // 等待所有任务执行完毕或者超时
    if (!executor.awaitTermination(timeout, TimeUnit.SECONDS)) {
        // 如果超时,可以选择做一些处理
        System.out.println("Some tasks were not terminated");
    }
} catch (InterruptedException e) {
    // 当前线程被中断时的处理逻辑
    e.printStackTrace();
}

结尾

本文对java线程池的分析就到这里,如有不正,欢迎指出。

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值