java中常见的六种线程池详解
之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java中常见的⼏种线程池,以及在jdk7加⼊的ForkJoin新型线程池⾸先我们列出Java中的六种线程池如下
线程池名称描述
FixedThreadPool核⼼线程数与最⼤线程数相同
SingleThreadExecutor⼀个线程的线程池
CachedThreadPool核⼼线程为0,最⼤线程数为Integer. MAX_VALUE
ScheduledThreadPool指定核⼼线程数的定时线程池
SingleThreadScheduledExecutor单例的定时线程池
ForkJoinPool JDK 7 新加⼊的⼀种线程池
在了解集中线程池时我们先来熟悉⼀下主要⼏个类的关系,ThreadPoolExecutor的类图,以及Executors的主要⽅法:
上⾯看到的类图,⽅便帮助下⾯的理解和查看,我们可以看到⼀个核⼼类ExecutorService , 这是我们线程池都实现的基类,我们接下来说的都是它的实现类。FixedThreadPool
FixedThreadPool线程池的特点是它的核⼼线程数和最⼤线程数⼀样,我们可以看它的实现代码在Executors#newFixedThreadPool(int)中,如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
我们可以看到⽅法内创建线程调⽤的实际是ThreadPoolExecutor类,这是线程池的核⼼执⾏器,传⼊的nThread参数作为核⼼线程数和最⼤线程数传⼊,队列采⽤了⼀个链表结构的有界队列。
这种线程池我们可以看作是固定线程数的线程池,它只有在开始初始化的时候线程数会从0开始创建,
但是创建好后就不再销毁,⽽是全部作为常驻线程池,这⾥如果对线程池参数不理解的可以看之前⽂章。
对于这种线程池他的第三个和第四个参数是没意义,它们是空闲线程存活时间,这⾥都是常驻不存在销毁,当线程处理不了时会加⼊到阻塞队列,这是⼀个链表结构的有界阻塞队列,最⼤长度是Integer. MAX_VALUE
SingleThreadExecutor
SingleThreadExecutor线程的特点是它的核⼼线程数和最⼤线程数均为1,我们也可以将其任务是⼀个单例线程池,它的实现代码是Executors#newSingleThreadExcutor() , 如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
上述代码中我们发现它有⼀个重载函数,传⼊了⼀个ThreadFactory的参数,⼀般在我们开发中会传⼊我们⾃定义的线程创建⼯⼚,如果不传⼊则会调⽤默认的线程⼯⼚我们可以看到它与FixedThreadPool线程池的区别仅仅是核⼼线程数和最⼤线程数改为了1,也就是说不管任务多少,它只会有唯⼀的⼀个线程去执⾏
如果在执⾏过程中发⽣异常等导致线程销毁,线程池也会重新创建⼀个线程来执⾏后续的任务
这种线程池⾮常适合所有任务都需要按被提交的顺序来执⾏的场景,是个单线程的串⾏。
CachedThreadPool
cachedThreadPool线程池的特点是它的常驻核⼼线程数为0,正如其名字⼀样,它所有的县城都是临时的创建,关于它的实现在Executors#newCachedThreadPool()中,代码如下: public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
从上述代码中我们可以看到CachedThreadPool线程池中,最⼤线程数为Integer.MAX_VALUE , 意味着他的线程数⼏乎可以⽆限增加。
因为创建的线程都是临时线程,所以他们都会被销毁,这⾥空闲线程销毁时间是60秒,也就是说当线程在60秒内没有任务执⾏则销毁
这⾥我们需要注意点,它使⽤了SynchronousQueue的⼀个阻塞队列来存储任务,这个队列是⽆法存储的,因为他的容量为0,它只负责对任务的传递和中转,效率会更⾼,因为核⼼线程都为0,这个队列如果存储任务不存在意义。
ScheduledThreadPool
ScheduledThreadPool线程池是⽀持定时或者周期性执⾏任务,他的创建代码wSchedsuledThreadPool(int)中,如下所⽰:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
我们发现这⾥调⽤了ScheduledThreadPoolExecutor这个类的构造函数,进⼀步查看发现ScheduledThreadPoolExecutor类是⼀个继承了ThreadPoolExecutor的,同时实现了ScheduledExecutorService接⼝,我们看到它的⼏个构造函数都是调⽤⽗类ThreadPoolExecutor的构造函数
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {java线程池创建的四种
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
从上⾯代码我们可以看到和其他线程池创建并没有差异,只是这⾥的任务队列是DelayedWorkQueue关于阻塞丢列我们下篇⽂章专门说,这⾥我们先创建⼀个周期性的线程池来看⼀下
public static void main(String[] args) {
ScheduledExecutorService service = wScheduledThreadPool(5);
// 1. 延迟⼀定时间执⾏⼀次
service.schedule(() ->{
System.out.println("schedule ==> 云栖简码-line");
},2, TimeUnit.SECONDS);
// 2. 按照固定频率周期执⾏
service.scheduleAtFixedRate(() ->{
System.out.println("scheduleAtFixedRate ==> 云栖简码-line");
},2,3,TimeUnit.SECONDS);
//3. 按照固定频率周期执⾏
service.scheduleWithFixedDelay(() -> {
System.out.println("scheduleWithFixedDelay ==> 云栖简码-line");
},2,5,TimeUnit.SECONDS);
}
上⾯代码是我们简单创建了newScheduledThreadPool,同时演⽰了⾥⾯的三个核⼼⽅法,⾸先看执⾏的结果:
⾸先我们看第⼀个⽅法schedule , 它有三个参数,第⼀个参数是线程任务,第⼆个delay表⽰任务执⾏延迟时长,第三个unit表⽰延迟时间的单位,如上⾯代码所⽰就是延迟两秒后执⾏任务
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
第⼆个⽅法是scheduleAtFixedRate如下, 它有四个参数,command参数表⽰执⾏的线程任务,initialDelay参数表⽰第⼀次执⾏的延迟时间,period参数表⽰第⼀次执⾏之后按照多久⼀次的频率来执⾏,最后⼀个参数是时间单位。如上⾯案例代码所⽰,表⽰两秒后执⾏第⼀次,之后按每隔三秒执⾏⼀次
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
第三个⽅法是scheduleWithFixedDelay如下,它与上⾯⽅法是⾮常类似的,也是周期性定时执⾏, 参数含义和上⾯⽅法⼀致。这个⽅法和scheduleAtFixedRate的区别主要在于时间的起点计时不同
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
scheduleAtFixedRate是以任务开始的时间为时间起点来计时,时间到就执⾏第⼆次任务,与任务执⾏所花费的时间⽆关;⽽scheduleWithFixedDelay是以任务执⾏结束的时间点作为计时的开始。如下所⽰
SingleThreadScheduledExecutor
它实际和ScheduledThreadPool线程池⾮常相似,它只是ScheduledThreadPool 的⼀个特例,内部只有⼀个线程,它只是将ScheduledThreadPool的核⼼线程数设置为了 1。如源码所⽰:
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
上⾯我们介绍了五种常见的线程池,对于这些线程池我们可以从核⼼线程数、最⼤线程数、存活时间三个维度进⾏⼀个简单的对⽐,有利于我们加深对这⼏种线程池的记忆。
FixedThreadPool SingleThreadExecutor CachedThreadPool ScheduledThreadPool SingleThreadScheduledExecutor
corePoolSize构造函数传⼊10构造函数传⼊1
maxPoolSize同corePoolSize1Integer. MAX_VALUE Integer. MAX_VALUE Integer. MAX_VALUE
keepAliveTime006000
ForkJoinPool
ForkJoinPool这是⼀个在JDK7引⼊的新新线程池,它的主要特点是可以充分利⽤多核CPU , 可以把⼀
个任务拆分为多个⼦任务,这些⼦任务放在不同的处理器上并⾏执⾏,当这些⼦任务执⾏结束后再把这些结果合并起来,这是⼀种分治思想。
ForkJoinPool也正如它的名字⼀样,第⼀步进⾏Fork拆分,第⼆步进⾏Join合并,我们先来看⼀下它的类图结构
ForkJoinPool的使⽤也是通过调⽤submit(ForkJoinTask<T> task) 或invoke(ForkJoinTask<T> task)⽅法来执⾏指定任务了。其中任务的类型是ForkJoinTask类,它代表的是⼀个可以合并的⼦任务,他本⾝是⼀个抽象类,同时还有两个常⽤的抽象⼦类RecursiveAction和RecursiveTask,其中RecursiveTask表⽰的是有返回值类型的任务,⽽RecursiveAction则表⽰⽆返回值的任务。下⾯是它们的类图:
下⾯我们通过⼀个简单的代码先来看⼀下如何使⽤ForkJoinPool线程池
/**
* @url: line
* @author: AnonyStar
* @time: 2020/11/2 10:01
*/
public class ForkJoinApp1 {
/**
⽬标:打印0-200以内的数字,进⾏分段每个间隔为10以上,测试forkjoin
*/
public static void main(String[] args) {
/
/ 创建线程池,
ForkJoinPool joinPool = new ForkJoinPool();
// 创建根任务
SubTask subTask = new SubTask(0,200);
// 提交任务
joinPool.submit(subTask);
//让线程阻塞等待所有任务完成在进⾏关闭
try {
joinPool.awaitTermination(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
joinPool.shutdown();
}
}
class SubTask extends RecursiveAction {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论