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.测试结果
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构造方法中的参数,你都还记得吗?我们一起来看一下,这里我通过截图进行说明:
我们可以看到:
- 最大线程数与核心线程数都是: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.测试结果
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构造方法中的参数,你都还记得吗?我们一起来看一下,这里我通过截图进行说明:
我们可以看到:
- 核心线程数,与最大线程数相等,都是指定的: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.测试结果
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构造方法中的参数,你都还记得吗?我们一起来看一下,这里我通过截图进行说明:
我们可以看到:
- 核心线程数,是:0
- 最大线程数,是:Integer.MAX_VALUE
- 线程空闲存活时间:60秒
- 使用直接交换队列:SynchronousQueue,不缓冲任务
结论讨论:
-
创建可缓存线程数量的线程池,工作线程数的创建无限制,没有节制,最大可达到:Integer.MAX_VALUE
-
使用直接交换队列:SynchronousQueue,不缓冲工作任务
-
在实际项目中我们不推荐使用它,原因是:
- 虽然通过大量工作线程,解决了并发提升业务处理能力的收益
- 但是无限制,没有节制的创建工作线程,我们知道操作系统对进程可创建的线程数量是有限制的
- 会有因为创建线程过多,而导致的线程溢出,内存溢出的风险
到此,我们通过Executors工具类演示了几种常见的线程池,最后结论讨论部分,我们都说在实际项目中,不推荐使用。那么在实际项目中,我们该如何合理的创建并使用线程池呢?我们放到下一篇文章中,再一起来分享吧