Java线程池学习记录

496 阅读11分钟

Java线程池学习记录

基础API

JUC提供的创建方式

// 创建一个定长的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
// 创建一个单线程线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 创建一个可缓存支持灵活回收的线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 创建一个支持周期执行任务的线程池
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1); 

相关接口

// 所使用的是接口ExecutorService的实现
public interface ExecutorService extends Executor

// Executor只规定这一个方法 交给线程池去执行一个任务
void execute(Runnable command);

// ExecutorService扩展了交给线程池去执行一个有返回值的任务
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);
// 尝试主动终止线程池中的所有正在执行的任务并返回未执行的任务列表,
List<Runnable> shutdownNow();
// 等待线程池执行完成已接收的任务,但不接收新任务 然后关闭
void shutdown();

实现类

// 最终被ThreadPoolExecutor实现
// JUC提供的4种都是和ThreadPoolExecutor有关
public ThreadPoolExecutor(
    int corePoolSize, // 核心线程池的大小,一旦创建不会被销毁,会进入缓存队列等待运行
    int maximumPoolSize, // 线程池能创建的最大线程数量。超过则采取拒绝策略
    long keepAliveTime, // 非核心线程能够空闲的最长时间,超时时间
    TimeUnit unit, // keepAliveTime的单位
    BlockingQueue<Runnable> workQueue, // 任务队列 用于存放被执行的任务
    ThreadFactory threadFactory, // 线程工厂,用来创建线程
    RejectedExecutionHandler handler // 任务拒绝策略
);
// ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
// LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
// SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
// ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
// ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛异常。
// ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试惹任务(重复执行此过程)
// ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

源码分析

ThreadPoolExecutor

JUC的代码注释个人感觉是非常详细,所以想了解JUC里面的类最好是阅读一下注释

/**
 * 在方法execute(Runnable) 中提交新任务时,
 * 如果运行的线程数小于corePoolSize,则会创建一个新线程来处理请求,即使其他工作线程处于空闲状态
 * 如果运行的线程超过corePoolSize但小于maximumPoolSize只有在队列已满的情况下才会创建新线程
 *
 * 默认情况下,即使核心线程最初也会创建并仅在新任务到达时启动,但是这可以使用方法prestartCoreThread()
 * 或者prestartAllCoreThreads()动态覆盖。如果你创建了一个非空的队列,大概需要预先启动线程。
 * 
 * 使用ThreadFactory创建新线程,如果没有指定则使用Executor下的DefaultThreadFactory
 * 通过它创建的线程都处于相同的ThreadGroup,并且具有相同的优先级和都是非守护进程的状态,通过提供不同
 * ThreadFactory,可以更改线程的名称、线程组、优先级、守护线程状态等。
 * 
 * 如果线程池中又多个corePoolSize线程,如果多余的线程空闲时间超过keepAliveTime,则会中止这些线程。
 * 这提供了一种在线程池未被积极使用时减小资源消耗的方法。使用Long.MAX_value TimeUnit.NANOSECONDS
 * 可以有效的使空闲线程在线程池关闭直接被中止。默认情况下,当存在多于corePoolSize线程时,Keep-alive才会生效
 * 但是方法allowCoreThreadTimeOut可以将此超时策略应用于核心线程。只要keepAliveTime值不为零。
 * 
 * 在Executor被关闭以及超出最大线程数和Queue的容量时,将启用拒绝策略
 *
 * 钩子函数
 * 此类提供了protected可重写的 beforeExecute(Thread, Runnable) 和 afterExecute(Runnable, Throwable)
 * 在执行每个任务之前和之后,这些可以用来操纵执行环境,例如重新初始化线程的局部变量、收集统计信息或者添加日志
 * 此外可以重写方法terminated()来在执行器完全中止后需要执行的特殊处理。
 * 如果钩子或回调方法抛出异常,内部工作线程可能会失败并突然中止
 */
public class ThreadPoolExecutor extends AbstractExecutorService {
   /**
     * 线程池状态ctl是一个包含两个概念字段的原子整数
     * workerCount : 有效线程数(2^29)-1(约5亿)
     * runState : 运行状态
     * 线程池生命周期
     * RUNNING:接收新任务和处理排队的任务
     * SHUTDOWN:结束 不接受新任务但处理排队的任务
     * STOP:停止 不接受新任务不处理排队的任务和中断正在进行的任务
     * TIDYING:整理 所有任务都已经中止,workerCount为零,转换到整理状态将会运行terminated()钩子方法
     * TERMINATED: 结束 terminated()执行完状态为结束 
     */
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
}

execute

其中主要的执行方法是Executor的execute(Runnable command)方法

// ThreadPoolExecutor
public void execute(Runnable command) {
    // 传入任务不能为空
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 工作线程数小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        // 提交给核心线程,提交成功就返回,提交失败重新获取ctl
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 检查线程池是否还在运行,并且向阻塞队列放置任务
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 如果线程池状态不为running,将任务从阻塞队列里面移除,启用拒绝策略
        if (!isRunning(recheck) && remove(command))
            reject(command);
        // 没有被拒绝的话,如果工作线程为0 则调用addworker提交任务
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 添加到非核心线程失败
    else if (!addWorker(command, false))
        reject(command);
}

thread.jpg 通过execute(Runnable command)的代码可以大概看出注释中所说的

  • 如果运行的线程数小于corePoolSize,则会创建一个新线程来处理请求,即使其他工作线程处于空闲状态
  • 如果运行的线程超过corePoolSize但小于maximumPoolSize只有在队列已满的情况下才会创建新线程

addWorker

这个出现多次的addWorker方法也可以大概猜测是承担着线程创建处理请求的功能。

private boolean addWorker(Runnable firstTask, boolean core) {
     retry:
     for (;;) {
         // 获取线程池状态
         int c = ctl.get();
         int rs = runStateOf(c);
         // 非运行 && !(关闭 && 传入任务是null && 工作队列不为空)
         // 非运行 并且 (线程池非关闭 或者 传入任务不为空 或者 工作队列为空)
         // 非运行 并且 不是(关闭状态等待处理Queue)
         // 非运行非结束 或者 非运行但是传入新任务 或者 非运行而且队列空了
         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 {
         // 在构造方法时拿到工厂创建了一个Thread
         w = new Worker(firstTask);
         // 拿出刚刚创建的Thread
         final Thread t = w.thread;
         if (t != null) {
             final ReentrantLock mainLock = this.mainLock;
             mainLock.lock();
             try {
                 int rs = runStateOf(ctl.get());
        // 线程池是运行状态 或者 线程池结束但是任务已经送到了Queue 启动一个Thread去帮助处理Queue
        // 这里可以配合上面workerCount为0时来理解,为0需要启动Thread来处理Queue里的任务
                 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;
 }

thread2.jpg 上面两层for循环主要是判断是否满足新加线程的条件,而后面主要就是创建Worker,创建线程来执行任务。根据代码可以看出影响线程创建的还是对应的上限(核心线程数、最大线程数)和 线程池的状态。

Worker

再来看看Worker这个类

/**
 * 该类主要维护运行任务的线程的中断控制状态,以及其他次要的记录。
 * 他可以用于唤醒等待任务的工作线程中断,而不是中断正在运行的任务中断
 * 为了防止还没运行就中断,我们在一开始将状态设置为负值并且在启动时清除。
 */
private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable {
    /** 对应的线程 null的话就是工厂创建失败了 */
    final Thread thread;
    /** 要执行的任务,可能会为空 */
    Runnable firstTask;
    /** 执行的任务个数 */
    volatile long completedTasks;
    /** 实现的run方法 */
    public void run() {
        runWorker(this);
    }
}

runWorker(this)

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 这里会把getState的状态置为0
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 任务不为空 或者从Queue获取到任务的任务不为空
        while (task != null || (task = getTask()) != null) {
            // 加锁
            // 在Worker的中断时先tryLock一下,如果已经有锁代表线程获取到Task,要保证Task的执行
            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置空
                task = null;
                // 统计任务数
                w.completedTasks++;
                w.unlock();
            }
        }
        // 没有突然完成 如果是报异常的话就不会走这里
        completedAbruptly = false;
    } finally {
        // 执行worker的结束处理
        processWorkerExit(w, completedAbruptly);
    }
}

可以看到会处理自己的任务或者从Queue取任务然后一直循环执行。 其实到这里还没有看到核心线程有什么特殊的。

getTask()

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);
        // 允许核心线程超时 || 工作线程大于核心线程
        // 这里判断了是否允许关闭线程 因为如果获取null的话线程会被关闭
        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;
        }
    }
}

可以看到其实是没有核心线程和非核心线程的差距的,只是在每次获取任务的时候根据核心线程数去判断是否需要关掉一部分线程,来释放资源。

processWorkerExit

回头看一下是怎么关闭的

/**
 * 为结束的worker进行清理和记录,假设workerCount已被调整以考虑退出,此方法从工作线程集中删除线程。
 * 如果由于用户任务异常而退出或者正在运行但是工作线程小于corePoolSize或者队列为空但是没有了工作线程
 * 则可能中止线程池或者替换工作线程
 */
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果completedAbruptly=false,说明是由getTask返回null导致的,WorkerCount递减的操作已经执行
    // 如果completedAbruptly=true,说明是由执行任务的过程中发生异常导致,需要进行WorkerCount递减的操作
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    // 加锁操作workers
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 尝试中止
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        // 如果是因为异常则直接新增一个线程
       	// 如果是正常的则判断是否保证了要求的最小线程数 如果没有则新增,有直接返回代表该线程执行结束
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
            // Queue不为null的话是要满足至少有一个线程执行的
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

tryTerminate()

final void tryTerminate() {
    for (;;) {
        // 运行状态的话直接放过 或者 还有任务需要执行 或者已经被其他线程改为中止状态,
        // 只有一个线程去执行钩子就可以
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        // 工作线程不为0的话就中断一个线程
        // 这里就是要陆续结束线程了
        if (workerCountOf(c) != 0) { // Eligible to terminate
            // 中断一个空闲线程 // 中断线程会导致线程被唤醒
            // 被唤醒的线程还是会走到这
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
	// 能走到这代表 又不能继续执行了,又没有工作线程了
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 选出一个来执行钩子函数(只会执行一遍)
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 钩子函数
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
        
}

关闭线程池

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 校验安全
        checkShutdownAccess();
        // for(;;) + CAS 设置状态为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断空闲的Worker
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}
// 中断空闲的Worker
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 所有的Worker
        for (Worker w : workers) {
            Thread t = w.thread;
            // 注意这里,这里就是所说的不中断已经获取到Task要运行的线程
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            // 只中断一个的话就跳出就好了
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

可以看到如果调用shutdown()关闭线程池,会中断等待任务的线程,但是正在处理任务的线程会继续把任务处理完,处理完后发现状态是SHUTDOWN而且Queue中没有任务则会结束,如果有Queue那么还会执行

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // for(;;) + CAS 置为STOP
        advanceRunState(STOP);
        // 中断Worker
        interruptWorkers();
        // 拿到Queue中剩余的任务
        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();
    }
}
// Worker下的
void interruptIfStarted() {
    Thread t;
    // getState >= 0代表启动了当前线程
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

shutdownNow()的话是直接STOP,然后中断所有Worker,返回Queue中的任务。

thread3.jpg