Executors 封装的四种线程池 各自有何特点,如何使用 超详细 代码动图演示
1.了解线程池 什么是线程池 为什么要用线程池
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销
----摘自 百度百科
扣下重点 :
- 线程池 是 一种 线程使用模式
- 频繁创建线程带来巨大的 调度开销 影响性能
- 线程池 维护 多个线程 , 但维护的线程数 并不是越多越好
线程池一种线程的使用模式,它帮线程使用者维护着多个线程 使用线程池可以 避免用户频繁创建线程带来的巨大开销,防止过分调度.但线程池维护的线程数并不是越多越好 需要我们合理的创建,使用线程池 因此 引出了 为什么不推荐使用 Executors 创建线程池
2.0 Executors的四种线程池 概览
2.1 FixedThreadPool 定长线程池
2.1.1 创建一个FixedThreadPool 线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool();
2.1.2 源码解析
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建一个线程池,该线程池重用在共享无界队列上运行的固定数量的线程。在任何时候,最多 nThreads 个线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。如果任何线程在关闭之前的执行过程中由于失败而终止,如果需要执行后续任务,新的线程将取代它。池中的线程将一直存在,直到显式关闭。参数:nThreads - 池中的线程数返回:新创建的线程池
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most 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 shutdown.
Params:
nThreads – the number of threads in the pool
Returns:
the newly created thread pool
Throws:
IllegalArgumentException – if nThreads <= 0
2.1.3 参数解析
- nThreads 核心线程数
- nThreads 最大线程数
- 0L 线程池中非核心线程空闲的存活时间大小
- TimeUnit.MILLISECONDS 线程空闲存活时间单位
new LinkedBlockingQueue() 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
FixedThreadPool 线程池的 核心线程数 等于 最大线程池数 也就是 只能有核心线程数 且等待队列 是 默认 队列长度为 Integer.MAX_VALUE的 LinkedBlockingQueue 阻塞队列
什么意思呢 看代码演示
2.1.4 代码演示
public class ExecutorsTest {
public static void main(String[] args) {
//创建核心线程数为10的 FixedThreadPool 线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {
int t = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程 : "+(++t));
}
});
int num = 0;
for (int i = 0; i < 100; i++) {
fixedThreadPool.execute(()->{
try {
System.out.println(Thread.currentThread().getName()+"正在运行");
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
我们创建一个核心线程数 为 10 的fixedThreadPool 线程池 循环提交 100个线程任务 可以看到动态图中 只会同时有十个线程在执行
且永远都是那10个线程在执行任务 为什么 先看下图 线程池工作原理
核心线程创建后就会一直存活 由于 我们创建的 fixedThreadPool 线程池核心线程数为 10 且最大线程数为10 等待队列的队列长度为
integer.MAX_VALUE 所以 同时只能有10个线程任务执行 其余的全部进入等待队列 核心线程执行完任务后再去队列中取等待执行的任务
2.1.5 线程池的用途
由于 FixedThreadPool 只有核心线程,所以可以用来控制线程最大并发 无论来多少线程 我只执行核心线程 其余的全部进入等待队列等待执行
2.2 ScheduledThreadPool 定时线程池
2.2.1 创建一个ScheduledThreadPool 线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);
2.2.2 源码解析
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
2.2.3 参数解析
- corePoolSize 核心线程数
- Integer.MAX_VALUE 最大线程数( 非核心线程数 )
- 0 线程池中非核心线程空闲的存活时间大小
- NANOSECONDS 线程空闲存活时间单位
- new DelayedWorkQueue() 无界的
队列
,用于放置实现了Delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走
2.2.4 代码演示
2.2.4.1 延迟执行线程任务
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
System.out.println(LocalTime.now());
//创建并执行在给定延迟后执行任务
scheduledExecutorService.schedule(()->{
System.out.println(LocalTime.now());
},
//延迟执行
3,
TimeUnit.SECONDS
);
}
}
2.2.4.2 周期执行线程任务
创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后initialDelay+period ,然后是initialDelay + 2 * period ,等等。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。 如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
System.out.println(LocalTime.now());
//周期执行任务
scheduledExecutorService.scheduleAtFixedRate(()->{
System.out.println(LocalTime.now());
},
//延迟第一次执行的时间
0,
//连续执行之间的时间
3,
//initialDelay和period参数的时间单位
TimeUnit.SECONDS
);
}
}
注意 : 如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。
什么意思呢 ? 如果一个任务的执行时间超过 周期时间 下一个任务不会执行 会等待上一个任务执行完毕后再去计算周期
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//周期执行任务
scheduledExecutorService.scheduleAtFixedRate(()->{
System.out.println(LocalTime.now());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
},
//延迟第一次执行的时间
0,
//连续执行之间的时间
3,
//initialDelay和period参数的时间单位
TimeUnit.SECONDS
);
}
}
2.2.5 线程池的用途
执行定时任务 或者 周期性 任务
2.3 CachedThreadPool 缓存线程池
2.3.1 创建一个CachedThreadPool 线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
2.3.2 源码解析
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
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 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 ThreadPoolExecutor constructors.
创建一个线程池,根据需要创建新线程,但在可用时将重用以前构造的线程。这些池通常会提高执行许多短期异步任务的程序的性能。如果可用,对执行的调用将重用以前构造的线程。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。六十秒内未使用的线程将被终止并从缓存中删除。因此,保持空闲足够长时间的池不会消耗任何资源。请注意,可以使用 ThreadPoolExecutor 构造函数创建具有相似属性但细节不同(例如超时参数)的池。
2.3.3 参数解析
- 0 核心线程数
- Integer.MAX_VALUE 最大线程数( 非核心线程数 )
- 60L 线程池中非核心线程空闲的存活时间大小
- TimeUnit.SECONDS 线程空闲存活时间单位
- new SynchronousQueue() 不存储元素的阻塞队列,也即单个元素的队列
2.3.4 代码演示
public class CachedThreadPoolTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
int finalI = i;
cachedThreadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行了 "+ finalI);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
由于没有核心线程 且等待队列为 SynchronousQueue() 不存储元素的阻塞队列,也即单个元素的队列
所以提交的线程任务全部会交给非核心线程任务执行 非核心线程任务的存活时长为 60 秒
2.3.5 线程池的用途
适用于处理大量、耗时少的任务。 每次提交线程任务都会立即有非核心线程去执行
2.4 SingleThreadExecutor单线程化线程池
2.4.1 创建一个SingleThreadExecutor线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
2.4.2 源码解析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
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 newFixedThreadPool(1) the returned executor is guaranteed not to be reconfigurable to use additional threads.
创建一个使用单个工作线程在无界队列上运行的 Executor。 (但请注意,如果该单线程在关闭前的执行过程中因失败而终止,则如果需要执行后续任务,则新线程将取代它。)任务保证按顺序执行,并且不会有多个任务处于活动状态在任何给定时间。与其他等效的 newFixedThreadPool(1) 不同,返回的执行程序保证不可重新配置以使用额外的线程。
2.4.3 参数解析
- 1 核心线程数
- 1 最大线程数( 非核心线程数 )
- 0L 线程池中非核心线程空闲的存活时间大小
- TimeUnit.MILLISECONDS 线程空闲存活时间单位
- new LinkedBlockingQueue()) 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
2.4.4 代码演示
public class SingleThreadExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
}
永远只有一个核心线程去执行线程任务
2.4.5 线程池的用途
所有的线程任务都将使用同一个线程( 核心线程创建后一直存活 ).在队列中有序等待执行