转载

Java并发指南12:深度解读 java 线程池设计思想及源码实现

本文首发于我的个人博客: https://h2pl.github.io/

欢迎阅览我的CSDN专栏:Java并发指南

https://blog.csdn.net/column/details/21961.html

相关代码会放在我的的Github: https://github.com/h2pl/

深度解读 java 线程池设计思想及源码实现

转自

https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

我相信大家都看过很多的关于线程池的文章,基本上也是面试必问的,好像我写这篇文章其实是没有什么意义的,不过,我相信你也和我一样,看了很多文章还是一知半解,甚至可能看了很多瞎说的文章。希望大家看过这篇文章以后,就可以完全掌握 Java 线程池了。

我发现好些人都是因为这篇文章来到本站的,希望这篇让人留下第一眼印象的文章能给你带来收获。

本文一大重点是源码解析,不过线程池设计思想以及作者实现过程中的一些巧妙用法是我想传达给读者的。本文还是会一行行关键代码进行分析,目的是为了让那些自己看源码不是很理解的同学可以得到参考。

线程池是非常重要的工具,如果你要成为一个好的工程师,还是得比较好地掌握这个知识。即使你为了谋生,也要知道,这基本上是面试必问的题目,而且面试官很容易从被面试者的回答中捕捉到被面试者的技术水平。

本文略长,建议在 pc 上阅读,边看文章边翻源码(Java7 和 Java8 都一样),建议想好好看的读者抽出至少 15 至 30 分钟的整块时间来阅读。当然,如果读者仅为面试准备,可以直接滑到最后的总结部分。

目录

  • 总览
  • Executor 接口
  • ExecutorService
  • FutureTask
  • AbstractExecutorService
  • ThreadPoolExecutor
  • Executors
  • 总结

总览

开篇来一些废话。下图是 java 线程池几个相关类的继承结构:

Java并发指南12:深度解读 java 线程池设计思想及源码实现

先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) 接口方法定义。

ExecutorService 也是接口,在 Executor 接口的基础上添加了很多的接口方法,所以一般来说我们会使用这个接口。

然后再下来一层是 AbstractExecutorService,从名字我们就知道,这是抽象类,这里实现了非常有用的一些方法供子类直接使用,之后我们再细说。

然后才到我们的重点部分 ThreadPoolExecutor 类,这个类提供了关于线程池所需的非常丰富的功能。

另外,我们还涉及到下图中的这些类:

Java并发指南12:深度解读 java 线程池设计思想及源码实现

同在并发包中的 Executors 类,类名中带字母 s,我们猜到这个是工具类,里面的方法都是静态方法,如以下我们最常用的用于生成 ThreadPoolExecutor 的实例的一些方法:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

另外,由于线程池支持获取线程执行的结果,所以,引入了 Future 接口,RunnableFuture 继承自此接口,然后我们最需要关心的就是它的实现类 FutureTask。到这里,记住这个概念,在线程池的使用过程中,我们是往线程池提交任务(task),使用过线程池的都知道,我们提交的每个任务是实现了 Runnable 接口的,其实就是先将 Runnable 的任务包装成 FutureTask,然后再提交到线程池。这样,读者才能比较容易记住 FutureTask 这个类名:它首先是一个任务(Task),然后具有 Future 接口的语义,即可以在将来(Future)得到执行的结果。

当然,线程池中的 BlockingQueue 也是非常重要的概念,如果线程数达到 corePoolSize,我们的每个任务会提交到等待队列中,等待线程池中的线程来取任务并执行。这里的 BlockingQueue 通常我们使用其实现类 LinkedBlockingQueue、ArrayBlockingQueue 和 SynchronousQueue,每个实现类都有不同的特征,使用场景之后会慢慢分析。想要详细了解各个 BlockingQueue 的读者,可以参考我的前面的一篇对 BlockingQueue 的各个实现类进行详细分析的文章。

把事情说完整:除了上面说的这些类外,还有一个很重要的类,就是定时任务实现类 ScheduledThreadPoolExecutor,它继承自本文要重点讲解的 ThreadPoolExecutor,用于实现定时执行。不过本文不会介绍它的实现,我相信读者看完本文后可以比较容易地看懂它的源码。

以上就是本文要介绍的知识,废话不多说,开始进入正文。

Executor 接口

/* 
 * @since 1.5
 * @author Doug Lea
 */
public interface Executor {
    void execute(Runnable command);
}

我们可以看到 Executor 接口非常简单,就一个 void execute(Runnable command) 方法,代表提交一个任务。为了让大家理解 java 线程池的整个设计方案,我会按照 Doug Lea 的设计思路来多说一些相关的东西。

我们经常这样启动一个线程:

new Thread(new Runnable(){
  // do something
}).start();

用了线程池 Executor 后就可以像下面这么使用:

Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());

如果我们希望线程池同步执行每一个任务,我们可以这么实现这个接口:

class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();// 这里不是用的new Thread(r).start(),也就是说没有启动任何一个新的线程。
    }
}

我们希望每个任务提交进来后,直接启动一个新的线程来执行这个任务,我们可以这么实现:

class ThreadPerTaskExecutor implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();  // 每个任务都用一个新的线程来执行
    }
}

我们再来看下 原文  http://h2pl.github.io/2018/05/29/concurrent12/

正文到此结束
Loading...