Java源码学习之高并发编程基础——线程池源码剖析

1.前言&目录

前言:

高并发编程基础——多线程的创建(上)高并发编程基础——多线程的创建(下)两篇多线程的创建介绍,本文将继续讲解实现多线程的其他方式——使用线程池。

通过上面的两篇文章,我们知道了在Java中是如何实现多线程编程的:

(1)直接继承Thread类并重写run()方法,但无法获取线程执行的返回值。

(2)实例化一个实现Runnable接口的类并重写run()方法,将此实例传入Thread实例,同样无法获取线程执行的返回值。

(3)通过实例化一个FutureTask实例,将其传入Thread实例,可以获取线程执行的返回值。

但是还有另外一种方式,那就是使用线程池,它既可以完成无返回值的任务,也可以完成有返回值的任务,接下来还是会以类和接口源码解读的方式进行Java的线程池的原理以及使用。

2.为什么要使用线程池

线程是稀缺资源,频繁的创建和销毁对系统来说是不可接受的,就如同一台电脑上,频繁打开多个应用程序然后关闭,重复下去会对电脑造成卡顿等情况。

(1)从系统内存角度分析,JVM默认会为每一个线程分配至少1MB的栈空间内存大小。也就是说,无限制的创建线程必定会造成系统内存溢出最终导致程序奔溃。

(2)从系统响应角度分析,使用线程池可以应对高并发的场景,而不是任务来一个才创建一个线程。创建好一定数量的工作线程,当任务来的时候可以直接执行,无需等待,提高系统响应速度。

目录

1.前言&目录

2.为什么要使用线程池

3.源码分析

3.1 ExecutorService接口源码剖析

3.2 Executor接口源码剖析

3.3 ThreadPoolExecutor源码剖析

3.3.1 构造函数

3.3.2 线程池运行状态

3.3.3 submit方法源码剖析

3.3.4 execute方法源码剖析

offer方法剖析

reject方法剖析

addWorker方法源码剖析

Worker工作线程源码剖析

runWorker方法源码剖析

 getTask方法源码剖析

3.3.5 源码剖析总结

3.4 使用线程池

4.总结

3.源码分析

在Java中,有许多种类型的线程池,本文将以常用的线程池ThreadPoolExecutor为例进行源码解读。

ThreadPoolExecutor提供了接收Runnable实例或者Callable实例(下文统一简称任务)的提交方法,接着会为这些任务创建一个Worker实例(下文统一简称工作线程)去执行任务中重写的run方法(无返回值)或call方法(有返回值)。

首先介绍的是线程池需要遵循的规范,即需要实现的接口。因为在Java是一门面向对象的语言,通过实现某一接口即表示该类拥有什么能力、需要遵循什么规范。正如祖国加入WTO协议后,可以享受进口货物关税降低的福利、增加同外国的贸易,同时也要遵循其要求,比如开放市场、尊重知识产权等等。

3.1 ExecutorService接口源码剖析

ExecutorService接口有三个提交任务的方法,分别是<T> Future<T> submit(Callable<T> task)、<T> Future<T> submit(Runnable task, T result)、Future<?> submit(Runnable task)。

在这三个抽象的sumbit方法中,均有一个共同点即返回值都是Future类型的,并且它是泛型表示能接收指定类型的结果。

Future在Java源码学习之高并发编程基础——多线程的创建(下)一文中介绍过,实现此接口的实现类如FutureTask可以获取线程执行的返回结果。即线程池通过这三个方法提交的任务可以获取执行后的返回结果。

public interface ExecutorService extends Executor {
    <T> Future<T> submit(Callable<T> task); // 提交有返回值的任务
    <T> Future<T> submit(Runnable task, T result);// 提交自定义返回值的任务
    Future<?> submit(Runnable task);// 提交一个返回值是null的任务
    void shutdown();// 关闭线程池
    List<Runnable> shutdownNow();
    boolean isShutdown();
    ...
}

除了三个submit方法外,该接口还有其他方法,比如shutdown方法的作用就是尝试关闭线程池,不过由于本文篇幅有限,因此仅以讲解线程池的大致的运行机制为主,也就是后文将会以提交任务的方法进行重点讲解。

 注意,ExecutorService接口继承了Executor接口,它的作用又是什么呢?

3.2 Executor接口源码剖析

Executor接口同样是ThreadPoolExecutor线程池遵循的规范。它提供了和submit方法不一样的execute方法。

Executor接口的execute方法用于线程池提交无法获取返回结果的任务,它的入参是Runnable实例,在Java源码学习之高并发编程基础——多线程的创建(上)一文已经介绍过,Runnable接口只有一个无返回值的抽象方法——run()方法。

即线程池可以通过此方法提交不能获取返回结果的任务。

public interface Executor {
    // 提交一个Runnable实例实现多线程,无法获取返回值
    void execute(Runnable command);
}

因此, ThreadPoolExecutor实际上拥有四个提交任务的方法,后文也将会重点讲解这些提交方法,这些方法中,尤其是execute方法,它不仅是其他三个submit方法的基础,而且线程池的基本运行规则也能从execute方法中一探究竟。

3.3 ThreadPoolExecutor源码剖析

本小节将会对ThreadPoolExecutor的源码进行解读,主要以解读其execute方法和submit方法为主。

不过在讲解这两种线程池提交任务的方法之前,需要先介绍它的构造函数。

3.3.1 构造函数

在以下伪代码中,ThreadPoolExecutor的构造函数有七个入参,它们分别是corePoolSize——核心线程数、maximumPoolSize——最大线程数、keepAliveTime——空闲线程最大存活时间、unit——存活时间的单位、workQueue——用于存储任务的阻塞队列、threadFactory——线程工厂、handler——拒绝任务的处理器。

并且做了一些基本的入参合法校验,corePoolSize不能小于0、maximumPoolSize不能小于或等于0、corePoolSize不能大于maximumPoolSize、keepAliveTime不能小于0,否则将直接抛出异常。

public class ThreadPoolExecutor extends AbstractExecutorService {
      // 存储工作线程的集合
      private final HashSet<Worker> workers = new HashSet<Worker>();
      // 拥有实时计算线程池状态和工作线程数量的成员属性
      private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
      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;
    }
}

这些入参相信也是面试中老生常谈的了,不过它们的作用将在后面结合submit和execute方法进行讲解。

除了构造函数的入参,我们还需要知道ThreadPoolExecutor的一些成员属性,AtomicInteger ctl和HashSet<Worker> workers。workers集合存储正是前面说的“工作线程”,工作线程负责执行任务,每当线程池创建一个工作线程执行任务时,会将工作线程放进workers集合,当(空闲的)工作线程没有任务执行了,就会从workers集合中移除掉并被销毁。

ctl则是一个可以实时计算出当前workers集合数量以及线程池的运行状态的一个原子对象,具体是如何实现我们无需知道,我们仅仅需要记住它拥有这种能力即可。

那么线程池的运行状态又是什么?

3.3.2 线程池运行状态

线程池的运行状态其实和FutureTask一样,它们并不是直接操控任务的执行,但是可以通过状态等控制变量去决定这些任务是否应该被执行。

这里对线程池的运行状态做简单论述:

RUNNING——接受新任务并处理排队的任务

SHUTDOWN——不接受新任务,但处理排队的任务

STOP——不接受新任务,不处理排队的任务

TIDYING——所有任务均已终止,工作线程数量归零,线程转换到状态TIDING,并调用钩子方法terminated()

TERMINATED——钩子方法terminated()将完成

本文由于以讲解线程池基本运作逻辑为主,因此对于线程池的运行状态不做深入探索,但是我们最好记住常用的RUNNING、SHUTDOWN和STOP代表着线程池此时能做什么、不能做什么。

3.3.3 submit方法源码剖析

经过本章节的前两个小节介绍后,线程池运作的资源也掌握了,因此可以开始讲解线程池的提交方法了。

在讲解提交任务的方法前,先了解ThreadPoolExecutor类结构,它继承了AbstractExecutorService类,并且三个submit方法都是在父类中,子类并没有重写。

其实在三个submit方法是中均是调用了execute方法去提交任务并交给线程池去执行,只是均把Runnable实例或者Callable实例的任务通过包装成RunnableFuture实例返回(RunnableFuture接口继承了Runnable接口和Future接口,即其实例拥有获取线程执行结果的能力)。

public abstract class AbstractExecutorService implements ExecutorService {
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
}

因此,我们这里只需要深入探索ThreadPoolExecutor#execute方法即可。

3.3.4 execute方法源码剖析

execute方法是ThreadPoolExecutor线程池提交任务的核心实现,此方法涵盖了线程池的基本运作——根据核心线程数、最大线程数创建一定数量的工作线程、利用阻塞队列存储任务、工作线程从阻塞队列获取任务执行......

在以下伪代码中,其实主要分了三种逻辑分支(由于用到了线程池的运行状态做判断,因此为了简化情景,假设这里的线程池状态都是RUNNING):

①比较当前工作线程的数量是否小于核心线程数,如果是的话直接通过addWorker(command, true)方法创建一个工作线程并返回。如果不是的话执行②的逻辑。

②通过BlockingQueue#offer方法将任务添加到队列尾部中去,如果添加成功,并且此时恰好工作线程数量为零,则调用addWorker(null, false)创建一个“钩子”的工作线程去执行任务。(q2:“钩子”工作线程是什么意思?)。如果队列已经满了,无法再添加了,则执行③的逻辑。

③当队列已经满了,无法继续添加的时候会调用addWorker(command, false)创建一个工作线程并返回,如果返回false会执行reject(command)方法拒绝此任务的处理。

public class ThreadPoolExecutor extends AbstractExecutorService {
    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); // 拒绝处理提交的任务
}

此时的我们仍然不清楚核心线程数、最大线程数、阻塞队列、线程工厂和拒绝策略处理器的具体作用,但是当讲解完addWorker、offer、reject方法后将会对它们有一定的认识。

offer方法剖析

offer方法是BlockingQueue接口的抽象方法,不同的实现类有不同的实现。了解offer方法后,我们将清楚线程池的核心参数——阻塞队列的作用

因此以其中一个实现类ArrayBlockingQueue为例子,在它内部有两个成员属性,一个是Object类型的数组常量items,它在初始化该阻塞队列实例的时候被确定数组长度,并且后面不能扩容,它正是负责存储线程池提交的任务。另一个是int类型的count变量,它的作用是实时统计当前阻塞队列中的任务数量,当往队列增加或者删除任务时都会跟着自增1或者自减1.

在以下offer方法的伪代码中可以看到,先判断count是否等于数组长度,如果是则表明队列已经满了,无法继续添加任务,返回false给外层调用方ThreadPoolExecutor#execute()方法表示由于队列已经满了,无法继续添加任务,因此会执行③额外创建工作线程处理“排队不过来”的任务的逻辑。

如果此时阻塞队列未满,则会执行enqueue方法将任务放进队列尾部,count变量自增1。

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>{
   int count; // 当前队列中实际的任务数量
   final Object[] items;// 装载任务的数据结构
   public ArrayBlockingQueue(int capacity) {
       this.items = new Object[capacity];// 初始化的时候就确定了队列容量
   }
   public boolean offer(E e) {
     checkNotNull(e);
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
        if (count == items.length) // 判断实际任务数量是否已经达到容量上限了
            return false;
        else {
            enqueue(e); // 将任务放进队列尾部,count自增1
            return true;
        }
    } finally {
        lock.unlock();
    }
    }
}

因此,在这里我们知道了阻塞队列的其中一个作用:将任务添加到队列尾部,队列满的时候告知线程池 “队列满了,你将这些排队不过来的任务额外创建工作线程吧”。

reject方法剖析

reject方法应对的场景是由于队列已满并且工作线程数量也达到上限导致无法通过addWorker方法创建额外的工作线程处理“排队不过来”的任务。了解了reject方法,我们将清楚线程池的核心参数——拒绝任务处理器的作用。

在reject方法中可以看到,会将任务实例和线程池ThreadPoolExecutor实例传入rejectedExecution方法,此方法是接口RejectedExecutionHandler的抽象方法,因此不同的拒绝任务处理器实例有不同的实现。

以默认的AbortPolicy实例为例子,它的rejectedExecution方法相当的“简单粗暴”,直接对外抛出异常说当前线程池拒绝处理此任务。

public class ThreadPoolExecutor extends AbstractExecutorService {
   final void reject(Runnable command) {
      handler.rejectedExecution(command, this);
   }
   // 当创建线程时,不指定拒绝任务执行处理器实例的话,则会默认创建AbortPolicy 
   public static class AbortPolicy implements RejectedExecutionHandler {
      public AbortPolicy() {}
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                    " rejected from "+ e.toString());
      }
   }
}

因此在这里,我们知道了 RejectedExecutionHandler的作用,就是当线程池的工作线程和阻塞队列都已满的情况下,无法继续接收处理新的任务了,就要通过它进行一个拒绝任务的处理操作,在这个例子中就是直接抛出异常。

addWorker方法源码剖析

addWorker方法中将解密上文中说的“工作线程”是如何创建和使用的,此方法将会解读任务的执行、读取阻塞队列任务、让空闲的工作线程销毁等核心工作。了解了addWorker方法,我们将清楚线程池的核心参数——核心线程数、最大线程数、线程工厂的作用。

由于addWorker方法中的判断仍然以线程池状态为RUNNING为前提,为了简化代码以及秉着浅入深出的原则,在这里假设线程池状态为RUNNING——即可以接收新任务并处理。

经过简化后的主要源码如下所示,在addWorker方法中有两个入参,第一个则是所说的“任务”,它是一个Runnable实例,第二个则是一个布尔类型的变量core,它有什么用呢?其实它就是负责判断当前线程池能否通过addWorker方法继续添加工作线程,可以看到,当core=true则是用核心线程数量去判断,当为false的时候则用最大线程数判断。

回顾execute方法的逻辑,在一般情况下,只有当前工作线程数量已经大于等于核心线程数并且调用offer方法返回false(队列已满的情况下)的时候会传递core等于false调用addWorker方法,即也就是说线程池中最多能创建的工作线程数量为最大核心线程数。

public class ThreadPoolExecutor extends AbstractExecutorService {
  private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
      ... 
      int wc = workerCountOf(c);
      // 根据第二个入参core判断应该使用核心线程数还是最大线程数判断
      if (wc >= (core ? corePoolSize : maximumPoolSize))
         return false;
      if (compareAndIncrementWorkerCount(c))
         break retry;
      ... 其他操作
    }
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
           if (t.isAlive()){
           // 检查工作线程的thread实例是否已经被调用start()方法启动了,已经
           // 被启动了则直接抛异常    
               throw new IllegalThreadStateException();
           }
           workers.add(w);// 将工作线程添加到workers集合中。
           workerAdded = true;
           ... 做一些其他可能将workerAdded变为false的操作
           if (workerAdded) {
               t.start(); // 启动工作线程
               workerStarted = true;
           }
    } finally {
        if (!workerStarted)
          addWorkerFailed(w); // 状态不对则将工作线程从workers集合中移除
    }
    return workerStarted;
  }
}

当工作线程数量校验通过后,可以看到会将任务——Runnable实例传递给Worker的构造函数并创建一个实例。

接着会从创建好的Worker实例获取一个类型为Thread的变量,当它不为空并且没有被调用start()方法启动线程时,则会将该Worker实例添加到workers集合中(用于ctl实时计算当前线程池有多少个工作线程),假设情况一切顺利的话,最后会调用该Thread的变量的start()方法该Thread变量。

而启动start()方法其实就是启动该工作线程,让它开始处理任务——通过addWorker方法提交的任务或者读取阻塞队列里的任务去执行。

那么其实这个“工作线程”,Worker对象的结构究竟是什么样的呢?为什么说它就是负责任务的执行呢?

Worker工作线程源码剖析

从下面伪代码看到,Worker继承了AbstractQueuedSynchronizer、实现了Runnable接口,即本质上Worker也是一个Runnable实例,因此它也重写了run方法。它的构造函数中接收一个Runnable实例,即通过回顾addWorker方法得知,其实线程池通过execute方法提交的任务最终会进入到Worker的构造函数将任务赋值给firstTask成员变量。

并且在Worker中还有另外一个成员属性——final Thread thread,当构造函数被调用时正是在这里通过将Worker实例传入去调用线程工厂ThreadFactory#newThread(Runnable r)的方法创建一个Thread实例。创建这个Thread实例的目的就是为了借助它的start()方法去启动工作线程。

当不指定ThreadFactory时,会使用默认的DefaultThreadFactory,在它的newThread方法创建的线程会重新设置线程名字、设置不是守护线程、设置普通的优先级。

public class ThreadPoolExecutor extends AbstractExecutorService {
    private final class Worker extends AbstractQueuedSynchronizer
                                              implements Runnable
    {
        // 实际上这个thread就是Worker实例自己,借助Thread#start()方法启动工作线程
        final Thread thread; 
        Runnable firstTask; // 就是通过ThreadPoolExecutor#execute方法提交的任务
    }
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    public void run() {
        runWorker(this); // 执行ThreadPoolExecutor#runWorker方法
    }    
}

至此,我们知道核心线程数、最大线程数以及线程工厂的作用了,当线程池此时的工作线程数量不大于核心线程数时则会创建核心线程数量的工作线程,接着当队列也满了,会为“排队不过来”的任务任务创建额外的工作线程但是数量不得大于最大线程数。而线程工厂则是在Worker中使用到,通过它去创建一个包含Worker实例本身的Thread实例,借助Thread#start()方法去启动工作线程。

而任务的执行就是在Worker#run()方法中,在这里将会获取任务(Worker实例的firstTask或者从阻塞队列获取),去调用它们的run()方法——这就是线程池中读取并执行任务的核心实现。

runWorker方法源码剖析

在runWorker方法中会读取任务并调用其run()方法——这就是工作线程执行任务的逻辑。

读取的任务来源有两个,一个是通过execute方法提交的任务,另一个则是阻塞队列里面的任务。当提交的任务为空时就会调用getTask()方法去从阻塞队列获取任务。

因此这里可以解读q2:“钩子”工作线程是什么意思?即在execute方法中分析过,当任务添加到队列后并且此时恰好工作线程数量为零,由于任务的执行都是通过工作线程的,因此需要调用addWorker(null,false)创建一个“钩子”工作线程,直接提交的任务为null就会在runWorker的getTask方法中从队列获取任务并执行。

当任务从这两个途径取到以后,就会直接调用任务(都是实现了Runnable接口,核心实现在run方法)的run()方法,即完成了任务的执行逻辑。

public class ThreadPoolExecutor extends AbstractExecutorService {
  final void runWorker(Worker w) {
    Runnable task = w.firstTask;
    w.firstTask = null;
    boolean completedAbruptly = true;
    try {
       while (task != null || (task = getTask()) != null) {
           try {
              ...
              try {
                task.run(); // 执行任务
              } catch (RuntimeException x) {
                throw x;
              }
           }finally {
              task = null;
              w.completedTasks++; // 统计该工作线程处理了多少任务
           }
           ...
       }
       completedAbruptly = false;  
    }finally {
       processWorkerExit(w, completedAbruptly);// 将工作线程从workers集合中移除
    }
  }
}

 当从两个途径获取的任务都是null时会退出while循环,接着进入finally语句块的processWorkerExit方法,在这里面会将当前的工作线程实例从workers集合移除掉,最后退出runWorker方法,即对应的上层调用Worker#run()方法也执行完毕——最后将回溯到addWorker方法,创建的Worker实例随着addWorker方法结束而销毁。

 讲解到这里,我们已经知道了核心线程数、最多线程数、线程工厂、阻塞队列、拒绝任务处理器的作用了,但是还有一个重要的参数——timeout,即空闲线程存活时间还不了解。那么接下来让我们探究这个getTask()方法,这里面便用到了timeout的核心参数,了解了getTask()方法,我们会知道线程池最终会保留多少个长久存活的工作线程。

 getTask方法源码剖析

getTask方法使用到了timeout的空闲线程等待时间、并且最终决定了线程池会保留核心线程数数量的工作线程。

在getTask方法中,主要逻辑如下所示,将当前线程池中工作线程的数量去判断是否大于了核心线程数,当大于的时候timed变量会变为true,那么这个timed变量有什么作用呢?其实它就是决定当此时超出核心线程数时,进入到该getTask方法的工作线程是否需要被销毁的决定条件之一。

当timed为true时,通过poll方法从队列获取任务,注意到poll的方法有两个入参,一个是线程池的其中一个核心参数——keepAliveTime,即空闲线程超时时间,第二个入参则是毫秒的单位将超时时间转为毫秒。也就说,通过poll方法(不同的阻塞队列有不同实现,还是以ArrayBlockingQueue为例)会当阻塞队列为空时,最多等待keepAliveTime的时间,当超时时最终会返回null值。

当timed为false时,则表示当前的工作线程数量并没有超过核心线程数,因此是通过take()方法从队列获取任务,还是以ArrayBlockingQueue为例,它的take方法会一直阻塞到队列有任务,否则会永远的等待下去,因此核心线程数量下的工作线程永远不会随着方法退出而被销毁。

public class ThreadPoolExecutor extends AbstractExecutorService {
   private Runnable getTask() {
     boolean timedOut = false;// 记录上一次从队列获取任务的时候是否超时了
     for (;;) {
        ... 线程池状态检查
        int wc = workerCountOf(c); // 当前工作线程数量
        // timed变量控制是否返回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;// 当从队列获取不到(poll方法)任务时表示超时了
         } catch (InterruptedException retry) {
           timedOut = false;
         }
     }
   }
}

另一个决定当前工作线程是否应该被销毁的条件之一就是timedOut,它表示的是在for循环体中上次从队列获取的任务是否为空,而前面也分析了只有poll方法会返回null,take方法是一个阻塞等待的方法。

因此timed和timedOut均为true并且此时的工作线程数量大于1或者队列已经为空(一般都是队列为空)了,那么就会返回null值给外层的runWorker方法——回顾runWorker的while循环体进入条件是从两个途径获取的任务都不是null。

也就是说,当工作线程数量已经超过核心线程数并且阻塞队列为空时,当前进入getTask方法的工作线程将获得null的返回结果,因而最终也会退出runWorker、run方法,最终回溯到addWorker方法,随着addWorker方法执行完毕而被JVM的垃圾回收器回收,即最终线程池只会保留核心线程数量的工作线程。

3.3.5 源码剖析总结

当讲解完ThreadPoolExecutor的构造函数、execute方法后,终于了解了线程池的核心参数作用以及它的一个主要运行逻辑。

通过核心线程数、最大线程数决定线程池长久存活的工作线程数量、以及某个时间最多存活的线程数量、将任务添加到队列中、根据此时工作线程数量去决定当前正在工作的线程是否需要在阻塞队列空载的时候被销毁等等...

3.4 使用线程池

接下来,将创建一个核心线程数为3,最大线程数为6,阻塞队列使用的是ArrayBlockingQueue,容量为1,空闲线程存活时间是60秒的线程池。

在以下伪代码中,定义了两种任务:实现Runnable接口的任务——无返回值、实现Callable接口的任务——有返回值。接着将一个返回值任务通过execute方法提交,将五个有返回值的任务通过submit方法提交。

从输出结果看,最多时创建了5个线程,因为这些方法相当简单并不会造成等待、阻塞,因此永远也无法创建最大线程数的线程。

public class MutipleThreadTest5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 6,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
        poolExecutor.execute(new Thread(new NoReturnValeCallable()));
        List<Future<Integer>> futureTaskList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Future<Integer> future = poolExecutor.submit(new HavaReturnValeCallable());
            futureTaskList.add(future);
        }
        for (int i = 0; i < futureTaskList.size(); i++) {
            System.out.println("获取到返回结果:" + futureTaskList.get(i).get());
        }
    }
    static class NoReturnValeCallable implements Runnable {
        @Override
        public void run() {
            System.out.println("没有返回值的线程[" + Thread.currentThread() + "]正在执行");
        }
    }
    static class HavaReturnValeCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("有返回值的线程[" + Thread.currentThread() + "]正在执行");
            return new Random().nextInt();
        }
    }
}
-------------------------------输出结果------------------------------------------
没有返回值的线程[Thread[pool-1-thread-1,5,main]]正在执行
有返回值的线程[Thread[pool-1-thread-1,5,main]]正在执行
有返回值的线程[Thread[pool-1-thread-3,5,main]]正在执行
有返回值的线程[Thread[pool-1-thread-2,5,main]]正在执行
有返回值的线程[Thread[pool-1-thread-4,5,main]]正在执行
有返回值的线程[Thread[pool-1-thread-2,5,main]]正在执行
获取到返回结果:-1212675415
获取到返回结果:1580475732
获取到返回结果:1012996487
获取到返回结果:854950602
获取到返回结果:308481251
-------------------------------输出结果------------------------------------------

4.总结

本文介绍了多线程的另外一种实现方式,就是通过线程池去提交任务。线程池是一种池化技术,创建一定数量的、长久存活的线程保存到线程池中,方便后续有新任务提交时可以复用已提前创建的线程资源,不仅节省程序内存还提高系统的响应速度。

并且以讲解核心源码的形式,了解了线程池六大核心参数的作用:

  • 核心线程数——线程池最终保存下来可以长久存活的线程数量。
  • 阻塞队列——存储任务,并且提供两个获取任务的方法,这两个方法是决定工作线程是否应该继续存活的核心方法。
  • 最大线程数——当队列已满并且工作线程未达到最多线程数时,会为后面来的任务额外创建数量最多为最大线程数-核心线程数的工作线程。
  • 线程工厂——创建实际的执行工作线程run方法的Thread实例。
  • 拒绝任务处理器对象——当工作线程已经达到上限、队列也满的情况,由它负责拒绝新任务的处理。
  • 空闲线程最大存活时间——当工作线程数量超过核心线程数并且队列已经空载,此时多余的工作线程将会等待一段时间后随着方法退出而被JVM垃圾回收器回收而销毁。

同时,也知道了线程池的大致运作原理:创建一定数量的工作线程去执行直接提交的任务或者轮循的读取阻塞队列正在排队的任务,多余的工作线程会在队列空载时被销毁,核心线程数量的工作线程则会一直阻塞等待队列直到有任务提交——即保证了线程池的在线活跃的状态,有新任务提交时可直接通过这些在线活跃的线程去执行任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值