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