快速掌握 Java 线程池技术


线程池的概念其实也没有那么深奥,可以简单的理解为就是一个容器内存放了多个空闲的线程,有新任务需要执行时,就从容器里面取出空闲线程,任务执行完毕后,再归还给容器。

之所以要使用线程池技术,主要还是因为创建一个新线程的成本比较高,程序底层需要跟操作系统进行交互。当程序中需要创建大量生存期限很短暂的线程时,就需要频繁的创建和销毁线程,这对系统的资源消耗,很有可能大于业务处理本身对系统的资源消耗,这就本末倒置了(因为我们之所以使用多线程,最终目的是为了提高业务处理能力)。为了尽可能的解决这种问题,我们就需要降低线程创建和销毁的频率,我们就需要使用线程池。

Java 的线程池实现技术其实非常简单,在真实的企业开发中,99% 的情况下,不会让你自己编码实现自定义的线程池,而是应该站在巨人的肩上,调用 Java 官方提供的 API 方法来实现。Java 官方提供的线程池 API 方法,学习和使用都非常简单,下面我就详细介绍一下吧。


一、使用 Executors 创建默认线程池

我们可以使用 Executors 中所提供的两个静态方法来创建线程池,方法如下:

静态方法名 说明
static ExecutorService newCachedThreadPool() 创建一个默认的线程池,线程池中线程数量最大为 int 的最大值
static newFixedThreadPool(int nThreads) 创建一个指定最大线程数量的线程池

代码实现:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {

        //创建一个默认的线程池,线程池中线程数量最大为 int 的最大值
        //ExecutorService executorService = Executors.newCachedThreadPool();

        //创建一个最大线程数量为 5 的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;

        //打印线程池中线程的数量
        System.out.println(pool.getPoolSize()); //当前线程池中的线程数量为 0

        //向线程池提交一个任务
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + " 执行了");
        });

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + " 执行了");
        });

        System.out.println(pool.getPoolSize()); //当前线程池中的线程数量为 2

        //关闭销毁线程池
        executorService.shutdown();
    }
}

从上面的代码可以看出:Java 官方提供的默认线程池,在启动的时没有创建空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,并不会销毁线程,而是归还到线程池中处于空闲状态,等待后续新任务的执行。


二、使用 ThreadPoolExecutor 创建自定义线程池

创建 ThreadPoolExecutor 线程池的构造方法参数如下:

第 1 个参数:核心线程数量
第 2 个参数:最大线程数量
第 3 个参数:空闲线程最大存活时间
第 4 个参数:存活时间的单位(分、秒、毫秒 ......)
第 5 个参数:任务队列
第 6 个参数:创建线程工厂(使用默认工厂即可:Executors.defaultThreadFactory())
第 7 个参数:任务的拒绝策略

任务拒绝策略 说明
ThreadPoolExecutor.AbortPolicy 多余的任务被丢弃并抛出 RejectedExecutionException 异常(默认策略)。
ThreadPoolExecutor.DiscardPolicy 多余的任务被丢弃,但是不抛出异常。这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy 丢弃队列中等待最久的任务,然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy 调用任务的 run() 方法绕过线程池直接执行。

下面进行代码演示,这里只演示任务拒绝策略为 AbortPolicy 和 CallerRunsPolicy 的代码,因为这两种比较常用。


1 使用 ThreadPoolExecutor.AbortPolicy 任务拒绝策略

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

public class ThreadPoolExecutorDemo01 {

    public static void main(String[] args) {
        //核心线程数量为1 ,最大线程池数量为3, 任务队列容量为1 ,空闲线程的最大存在时间为 20 秒
        ThreadPoolExecutor threadPoolExecutor 
            = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                                     new ArrayBlockingQueue<>(1) ,
                                     Executors.defaultThreadFactory() ,
                                     new ThreadPoolExecutor.AbortPolicy()) ;

        //当前提交 5 个任务,而该线程池最多可以处理 4 个任务,
        //当我们使用 AbortPolicy 这个任务拒绝策略的时候,就会抛出 RejectedExecutionException 异常
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "----> 执行了任务");
            });
        }
        
        //关闭销毁线程池
        threadPoolExecutor.shutdown();
    }
}

/*
可以将以下代码用 try catch 包裹,处理异常
threadPoolExecutor.submit(() -> {
    System.out.println(Thread.currentThread().getName() + "----> 执行了任务");
});
*/

2 使用 ThreadPoolExecutor.CallerRunsPolicy 任务拒绝策略

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

public class ThreadPoolExecutorDemo02 {

    public static void main(String[] args) {
        //核心线程数量为1 ,最大线程池数量为3, 任务队列容量为1 ,空闲线程的最大存在时间为 20 秒
        ThreadPoolExecutor threadPoolExecutor 
            = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                                     new ArrayBlockingQueue<>(1) ,
                                     Executors.defaultThreadFactory() ,
                                     new ThreadPoolExecutor.CallerRunsPolicy()) ;

        //当前提交 5 个任务,而该线程池最多可以处理 4 个任务
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "----> 执行了任务");
            });
        }
        
        //关闭销毁线程池
        threadPoolExecutor.shutdown();
    }
}

/*
控制台输出结果如下:
pool-1-thread-1----> 执行了任务
pool-1-thread-3----> 执行了任务
pool-1-thread-2----> 执行了任务
pool-1-thread-1----> 执行了任务
main----> 执行了任务

通过控制台的输出结果发现:
第 5 个任务没有被线程池中的线程执行,而是绕过线程池调用 run 方法,在 main 线程中执行。
*/

到此为止,Java 官方提供的线程池技术,已经介绍完毕。以上只是简单代码的演示,实际工作中可根据具体业务需求进行改造。另外在实际工作中,线程池一般情况下都不会进行手动编码关闭销毁,线程池的生命周期跟整个系统的生命周期相同。



相关