高级并发编程系列三(线程池:案例源码解读)

98 阅读7分钟

1.引子

前面通过两篇文章分享,我们主要聚焦在线程池概念理解,和设计实现一个线程池需要考虑的一些问题。

那么今天这一篇,我们通过juc包中提供的Executors工具类,创建一些线程池入门案例,直观的体验并发编程中线程池的使用,当然我们需要结合前面两篇内容:线程池的应用场景、设计实现线程池核心理念。一起来看这些入门案例就更好了。

那就让我们开始吧

#考考你:
1.在你们的项目中,有需要并发编程的应用场景吗?
2.如果有,需要使用线程池,那么你是如何创建并使用线程池的呢?

2.案例

2.1.拥有单个线程的线程池

世间事情真是有点意思,线程池怎么能只有一个线程呢?可是回头一想,为什么不可以呢?对吧,我就喜欢一个线程独来独往......

2.1.1.案例代码

/**
 * 拥有单个线程的线程池案例
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/9/20 11:41
 */
public class SingleThreadPoolDemo {
​
    public static void main(String[] args) {
        // 1.创建拥有单个线程线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
​
        // 2.通过线程池提交执行任务
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task());
        }
​
        // 3.释放线程池资源
        executorService.shutdown();
    }
​
}
​
/**
 * 任务
 */
class Task implements Runnable{
​
    @Override
    public void run() {
        System.out.println("当前正在处理任务线程:" + Thread.currentThread().getName());
        System.out.println("--------------------任务处理中------------------------");
    }
​
}

2.1.2.测试结果

image.png

2.1.3.源码解读

我们通过下面这一行代码,创建了拥有1个线程的线程池,在newSingleThreadExecutor()方法内部,是如何实现的呢?我们一起来看一下

// 1.创建拥有单个线程线程池
 ExecutorService executorService = Executors.newSingleThreadExecutor();

newSingleThreadExecutor方法源码

/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

源码解读

翻译注释第一句话,我们知道对于newSingleThreadExecutor方法,该方法只创建单个工作线程将任务缓冲到无界队列中

从方法内部,我们可以看到它其实是直接创建了ThreadPoolExecutor线程池实例。对于线程ThreadPoolExecutor构造方法中的参数,你都还记得吗?我们一起来看一下,这里我通过截图进行说明:

image.png

我们可以看到:

  • 最大线程数与核心线程数都是:1
  • 因为最大线程数与核心线程数相等,时间参数无意义
  • 使用无界队列:LinkedBlockingQueue缓冲任务

结论讨论:

  • 创建单个线程的线程池中,永远只会有且仅有一个工作线程

  • 在实际项目中我们不推荐使用它,原因是:

    • 单个工作线程,解决不了通过并发提升业务处理能力的收益
    • 使用无界队列缓冲任务,如果任务处理不及时,会有因为任务堆积而内存溢出的风险

2.2.拥有固定多个线程的线程池

2.2.1.案例代码

/**
 * 创建固定线程数量的线程池案例
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/9/20 13:13
 */
public class FixedThreadPoolDemo {
​
    public static void main(String[] args) {
        // 1.创建3个工作线程的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
​
        // 2.通过线程池提交执行任务
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task());
        }
​
        // 3.释放线程池资源
        executorService.shutdown();
​
    }
}
​
/**
 * 任务
 */
class Task implements Runnable{
​
    @Override
    public void run() {
        System.out.println("当前正在处理任务线程:" + Thread.currentThread().getName());
        System.out.println("--------------------任务处理中------------------------");
    }
​
}

2.2.2.测试结果

image.png

2.2.3.源码解读

我们通过下面这一行代码,创建了拥有3个线程的线程池,在newFixedThreadPool()方法内部,是如何实现的呢?我们一起来看一下

// 1.创建3个工作线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);

newFixedThreadPool方法源码

/**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

源码解读

翻译注释第一句话,我们知道对于newFixedThreadPool方法,该方法创建指定的固定工作线程将任务缓冲到无界队列中

从方法内部,我们可以看到它其实是直接创建了ThreadPoolExecutor线程池实例。对于线程ThreadPoolExecutor构造方法中的参数,你都还记得吗?我们一起来看一下,这里我通过截图进行说明:

image.png

我们可以看到:

  • 核心线程数,与最大线程数相等,都是指定的:nThreads
  • 因为最大线程数与核心线程数相等,时间参数无意义
  • 使用无界队列:LinkedBlockingQueue缓冲任务

结论讨论:

  • 创建固定线程数量的线程池,永远只会有指定数量的工作线程

  • 在实际项目中我们不推荐使用它,原因是:

    • 虽然有多个工作线程,解决了并发提升业务处理能力的收益
    • 但是使用无界队列缓冲任务,如果任务处理不及时,还是会有因为任务堆积而内存溢出的风险

2.3.可缓存(不限制 线程数)的线程池

2.3.1.案例代码

/**
 * 创建可缓存线程池案例
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/9/20 13:29
 */
public class CachedThreadPoolDemo {
​
    public static void main(String[] args) {
​
        // 1.创建可缓存工作线程的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
​
        // 2.通过线程池提交执行任务
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task());
        }
​
        // 3.释放线程池资源
        executorService.shutdown();
​
    }
}
​
/**
 * 任务
 */
class Task implements Runnable{
​
    @Override
    public void run() {
        System.out.println("当前正在处理任务线程:" + Thread.currentThread().getName());
        System.out.println("--------------------任务处理中------------------------");
    }
​
}

2.3.2.测试结果

image.png

2.3.3.源码解读

我们通过下面这一行代码,创建了可缓存线程的线程池,在newCachedThreadPool()方法内部,是如何实现的呢?我们一起来看一下

 // 1.创建可缓存工作线程的线程池
ExecutorService executorService = Executors.newCachedThreadPool();

newCachedThreadPool方法源码

/**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

源码解读

翻译注释第一句话,我们知道对于newCachedThreadPool方法,该方法创建可缓存工作线程使用直接交换队列SynchronousQueue,不缓冲任务

从方法内部,我们可以看到它其实是直接创建了ThreadPoolExecutor线程池实例。对于线程ThreadPoolExecutor构造方法中的参数,你都还记得吗?我们一起来看一下,这里我通过截图进行说明:

image.png

我们可以看到:

  • 核心线程数,是:0
  • 最大线程数,是:Integer.MAX_VALUE
  • 线程空闲存活时间:60秒
  • 使用直接交换队列:SynchronousQueue,不缓冲任务

结论讨论:

  • 创建可缓存线程数量的线程池,工作线程数的创建无限制,没有节制,最大可达到:Integer.MAX_VALUE

  • 使用直接交换队列:SynchronousQueue,不缓冲工作任务

  • 在实际项目中我们不推荐使用它,原因是:

    • 虽然通过大量工作线程,解决了并发提升业务处理能力的收益
    • 但是无限制,没有节制的创建工作线程,我们知道操作系统对进程可创建的线程数量是有限制的
    • 会有因为创建线程过多,而导致的线程溢出,内存溢出的风险

到此,我们通过Executors工具类演示了几种常见的线程池,最后结论讨论部分,我们都说在实际项目中,不推荐使用。那么在实际项目中,我们该如何合理的创建并使用线程池呢?我们放到下一篇文章中,再一起来分享吧