JUC-线程池源码简析

一、线程池介绍

Java5开始,在util下提供了一个包,叫做JUC(java.util.concurrent),里面提供了关于多线程、并发的一些工具包。例如锁、多线程等工具都在这个包中。

我们知道一个线程的创建、销毁过程是会消耗系统性能的,需要使用cpu,占用内存,当频繁大量的创建销毁线程,这个消耗累积到会影响系统性能,为了解决这一类问题,出现了“池化”的概念,“池化”意为将资源放进一个池子内,需要的时候就从池中取,不用的时候就返还池中以便其它复用,实践的例子很多,例如数据库连接池、缓冲池以及本次讲解的线程池等,中心思想就是复用这些连接,避免频繁的创建销毁,提高性能。

juc中的线程池的类关系如下图:

Executor.class是顶层接口类,只定义了一个方法:execute()方法,入参为Runnable接口;

ExecutorService.class是继承Executor的接口,加入了一些方法submit()、shutdown()、shutdownNow()等方法的定义;

AbstractExecutorService抽象类实现了ExecutorService部分接口,比如submit()等方法;

ScheduleExecutorService接口继承ExecutorService,顾名思义,增加了一些计划执行函数,实现周期行执行任务的功能;

ThreadPoolExecutor类就是我们常用的线程池工具类了,它继承于抽象类AbstractExecutorService,实现了线程池的任务、线程以及队列等功能,接下来我们详细介绍ThreadPoolExecutor的功能实现。

二、线程池源码

如何使用线程池?假设我们有如下图线程池实例代码,可以看到大致创建线程池并且启动的大概逻辑为创建,执行:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExecutorTest {
    public static void main(String[] args) {
        class MyRunnable implements Runnable {
            @Override
            public void run() {
                System.out.println("Running----");
            }
        }
        //创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                10,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10), new ThreadPoolExecutor.DiscardPolicy());
        //加入执行任务
        executor.submit(new MyRunnable());
    }
}

线程池构造参数含义?可以看到创建一个线程池,构造方法中需要传入很多参数,每个参数都有很重要的含义,下表列出了每个参数的含义:

参数名称 含义 备注

int corePoolSize

核心线程 线程池核心线程数,包括空闲线程

int maximumPoolSize

最大线程数 线程池所允许的最大线程数量

long keepAliveTime

线程存活时间 线程空闲时间超过该值则销毁(对核心线程起作用需配置参数)

TimeUnit unit

时间单位 线程空闲存活时间单位

BlockingQueue<Runnable> workQueue

阻塞队列 当核心线程用尽,将新任务加入队列

ThreadFactory threadFactory

线程工厂 创建线程的工厂

RejectedExecutionHandler handler

拒绝策略 当队列满时,根据该策略处理新到达的任务

注:boolean allowCoreThreadTimeOut决定keepAliveTime是否对核心线程有作用;

ThreadPoolExecutor提供了四种拒绝策略

  • 1.AbortPolicy:默认策略,丢弃任务,并且抛出RejectedExecutionException;
  • 2.DiscardPolicy:丢弃任务,什么都不做;
  • 3.DiscardOldestPolicy:丢弃老任务,执行该新任务;
  • 4.CallerRunsPolicy:调用者线程执行该任务;

注:也可以自定义拒绝策略,实现RejectedExecutionHandler接口。

ThreadPoolExecutor提供四种基础阻塞队列:顶层是BlockingQueue接口

  • 1.ArrayBlockingQueue:有界阻塞队列,底层为数组;
  • 2.LinkedBlockingQueue:无界或有界阻塞队列,底层为链表;
  • 3.SynchronousQueue:不存储元素,一个线程执行元素插入,需等待另一个线程执行移除元素,否则阻塞插入操作;
  • 4.PriorityBlockingQueue:无界的,带有优先级的阻塞队列;

注:队列一般需要设置上限,需要注意无界队列,如果过多的任务堆积于队列中,有oom风险。

ThreadPoolExecutor线程池状态流转:

线程池中基本成员参数?在线程池中维护了一些参数,例如状态、线程组以及锁等信息,如下图所示:

    //32位,前3位代表线程池状态,后29位代表线程池数量    
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //线程数量位32 - 3 = 29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //线程数容量000 11111111111111111111111111111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    //各个运行状态标志(值:running<~<terminated)
    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;

    //获取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;
    //锁,增加线程数、完成任务数等使用
    private final ReentrantLock mainLock = new ReentrantLock();
    //线程的容器
    private final HashSet<Worker> workers = new HashSet<Worker>();
    //锁的条件功能
    private final Condition termination = mainLock.newCondition();
   
    //线程数量峰值
    private int largestPoolSize;
    //完成的任务数量
    private long completedTaskCount;
    //创建线程的工厂
    private volatile ThreadFactory threadFactory;
    //拒绝策略句柄
    private volatile RejectedExecutionHandler handler;
    //空闲线程存活时间
    private volatile long keepAliveTime;
    //是否主线程也有过期时间
    private volatile boolean allowCoreThreadTimeOut;
    //核心线程数量
    private volatile int corePoolSize;
    //最大线程数量
    private volatile int maximumPoolSize;

线程池启动过程?当我们创建一个线程池后,添加并执行任务时:

1.首先判断当前线程的数量是否小于核心线程数量,如果小于,则直接创建核心线程并执行此任务,如果大于等于,则尝试把此任务加入到阻塞队列;

2.如果加入阻塞队列成功,则等待空闲线程来阻塞队列拉取任务并执行;

3.如果加入阻塞队列失败,则说明队列已经满了,这时候需要判断当前线程数量是否小于最大线程数,如果小于,则创建非核心线程并执行该任务;

4.如果大于等于最大线程数,则使用拒绝策略处理该任务;

线程池的大概执行逻辑如上步骤,接下来我们看代码。我们知道,线程池的入口执行方法是submit()以及execute(),前者实际上也是调用的后者,因此我们从execute()方法入口开始介绍执行过程:

    public void execute(Runnable command) {
        if (command == null)//传入的任务为null,直接抛出空指针异常
            throw new NullPointerException();
        int c = ctl.get();//获取ctl,拿到线程池状态、线程数量
        //1.获取线程数量,和核心线程数量比较
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//小于核心线程,则创建核心线程,并且把该任务加入到该线程
                return;
            c = ctl.get();//创建核心线程失败(可能线程池非运行状态、核心线程刚满),重新获取ctl
        }
        //2.如果超出核心线程数,尝试加入队列
        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);
        }
        //3.非运行或者队列满,则创建非核心线程执行该任务
        else if (!addWorker(command, false))
            reject(command);//超出最大线程数量或非运行状态,拒绝策略处理
    }

 可以看出主要判断逻辑为:1.核心线程是否满;2.队列是否满;3.是否超过最大线程数;

上述过程的流程图如下图所示:

 上述过程中,一个重要的方法addWorker()是新建一个worker,并把任务放入该worker中执行,下面我们分析该方法的执行过程:

private boolean addWorker(Runnable firstTask, boolean core) {
        //1.增加线程数量
        retry://goto 标识
        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()))//如果线程池非运行状态,且是停止状态、任务为空、队列为空,则返回false,添加失败
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))//规则校验线程数量
                    return false;
                if (compareAndIncrementWorkerCount(c))//CAS增加线程数量
                    break retry;//增加线程数量成功,跳出外循环
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)//增加线程数量失败,线程池状态改变则开始外循环,状态未变则开始内循环
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        //2.新建worker,执行线程任务
        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;
    }

 当在addWorker()方法中添加成功后,执行了start()函数,会去调用Worker类的run()方法,run()方法调用runWoker()方法,执行过程如下:

    //Woker方法重写的run()方法
    public void run() {
        runWorker(this);
    }

    //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 {
            while (task != null || (task = getTask()) != null) {//取本身任务、从队列获取任务;getTask方法很重要!!!
                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);
        }
    }

worker执行最后会对该线程做相应处理,包括cas增加完成任务数、尝试终止线程池、是否补线程等操作: 

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();//意外情况直接减少线程数量

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;//cas增加完成任务数
            workers.remove(w);//移除该worker
        } finally {
            mainLock.unlock();
        }

        tryTerminate();//尝试终止线程

        int c = ctl.get();
        //如果线程运行中,且正常结束,判定是否需要补充线程
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

线程池如何停止?在上方线程池状态流转图中可以看到,线程池可以通过shutdown()、shutdownNow()方法使线程池到达shutdown、stop停止状态,在运二者区别在于对于队列以及执行中的任务的处理方式,前者会等待队列及正在运行的任务完成才会执行退出逻辑,后者会终止正在运行的任务,剔除并返回所有队列中的任务,是比较粗暴的,下面介绍两种结束方式:

shutdownNow()方法执行逻辑?设置线程池状态为STOP,中断所有未中断线程,移除所有任务并返回,

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);//CAS设置状态为STOP
            interruptWorkers();//中断所有线程
            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;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
            }
        }
    }

对于队列中未执行的任务,直接抛弃并返回,那对于正在执行的任务是如何处理呢?文章中部图runWoker()方法中可以看到循环获取当前线程和阻塞队列里的线程,然后加锁、执行、结束放锁。运行中的线程改变了中断标志,如果处于阻塞状态(IO阻塞)则会抛异常,然后结束本线程,如果是正常执行线程,则执行完毕后退出。

如果在getTask()方法返回null,则线程完毕,方法如下,可以看到开始会判断线程池的状态,shutdownNow()此时已经将下线程池标注为STOP状态了。

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

shutdown()方法执行逻辑?相比于shutdownNow()方法,则更加优雅一些,代码如下:

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

中断方法如下,可以看到有一个加锁操作,而我们反过头来看到,runWorker()方法,执行中的方法先获取到锁了,因此执行中的任务,这里是无法终止的。

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

三、固定线程池

juc包中提供了一个类Executors,它提供了一些特殊固定功能的线程池,主要包括四种:

1.newSingleThreadPool:单一线程池,有且仅有一个线程;LinkedBlockingQueue是无界阻塞队列,队列可存放的大小为Integer.MAX_VALUE

2.newFixedThreadPool:固定数量线程的线程池,LinkedBlockingQueue是无界阻塞队列,队列可存放的大小为Integer.MAX_VALUE;

3.newCachedThreadPool:缓冲线程池,核心线程0,最大线程池数量是Integer.MAX_VALUE,空闲时间60s,使用SynchronousQueue,该队列不存储值;

4.newScheduledThreadPool:定时调度的线程池;

四、注意事项

1.合理配置线程数

线程数的多少直接关乎到线程池的性能效率以及对系统的影响,线程数量配置过多,一方面增加系统负载,另一方面如果任务不多会浪费线程资源,如果过少,任务过多怎会进入队列,可能延迟完成时间。首先需要分析我们的业务是IO密集型还是CPU密集型,根据实际业务情况,配置合理的线程数。

IO密集型业务主要是阻塞进行IO操作,比较耗时,因此需要配置多的线程数,弥补阻塞的时间,一般配置为CPU核数*2;

CPU密集型业务是进行大量的运算,多核的CPU可以提高计算速度,少量配置线程数,这样增加多核计算时间机率,因为相比之下线程切换可能更加耗时,一般配置CPU核数+1的线程数量;

上面的建议只是一个参考的方向,实践中还需要考虑业务的并发量,每个任务的执行时间,以及任务执行时间和利用cpu的时间比例,结合来考量线程数量的配置。

2.优雅关闭线程池

线程池中提供了两种停止线程池的方法:shutdown()、shutdownNow(),两者执行后的线程池状态在上面图中已经描述了,为了保证任务合理的关闭,我们应该选取第一种方式关闭,停止接收外部任务,执行完阻塞队列和正在执行的任务,然后,在通过方法awaitTermination()阻塞主线程,等待子线程任务完成后,优雅的关闭。

3.谨慎使用Executors提供的特殊线程池

Executors提供的几种特殊线程池,虽然省去了传入部分参数的麻烦,但是里边是有一部分的风险的。

单一或者固定数量的线程池,使用几乎无界的阻塞队列,如果任务执行很慢,但是任务很多,这个队列会急剧增加,可能会占用大量内存,并且因为在使用无法剔除,导致oom。

缓冲线程池,队列不会存储任务,当任务量急剧增多,会瞬间创建大量的线程,这个也是很危险的。虽然我们前面已经根据业务评估使用不同的线程池,但是这些风险还是存在的,因此估计使用自定义的线程池。

五、资源地址

官网:http://www.java.com

文档:《Thinking in java》jdk1.8版本源码

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章