【多线程】创建线程池有几种方式
网上的文章一般会说,创建线程池基本上是2种方式。ThreadPoolExecutor或者Executors。本文就是通过剖析源码,看下它们的实现。
ThreadPoolExecutor
ThreadPoolExecutor是一种自定义线程池的方式。使用ThreadPoolExecutor的方式为new ThreadPoolExecutor(…),构造函数中传入需要的几个参数,通过参数自定义线程池。
其中涉及7个参数,我将构造函数的参数描述粘贴如下:
* @param corePoolSize the number of threads to keep in the pool, even* if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param maximumPoolSize the maximum number of threads to allow in the* pool* @param keepAliveTime when the number of threads is greater than* the core, this is the maximum time that excess idle threads* will wait for new tasks before terminating.* @param unit the time unit for the {@code keepAliveTime} argument* @param workQueue the queue to use for holding tasks before they are* executed. This queue will hold only the {@code Runnable}* tasks submitted by the {@code execute} method.* @param threadFactory the factory to use when the executor* creates a new thread* @param handler the handler to use when execution is blocked* because the thread bounds and queue capacities are reached
corePoolSize,直接翻译就是核心池大小,也有文章叫他核心线程数[1]。这个参数表示线程池中不会死亡或者说被回收的线程数量。
maximumPoolSize,最大池大小,也有文章叫他最大线程数[1]。这个参数表示线程最多有多少线程。注意,这个最大线程数量中包括了不会死亡的核心线程,也包括了其他的会死亡的线程(在线程池空闲一段时间后死亡),它由这两者组成。
keepAliveTime ,生存时间。这个参数就是说当任务完成以后,并且后面没有任务了,此时线程池空闲了,如果此时maximumPoolSize > corePoolSize,说明有一些线程应该在空闲一段时间后死亡。keepAliveTime 就是指定它们空闲多久后死亡。
unit ,单位,很容易理解,是死亡时间的时间单位。范围为从天到纳秒[1]。
TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒
workQueue ,任务队列。线程池有一个maximumPoolSize最大线程数,当同一时间任务数量超过最大线程数的数量,线程池暂时无法处理,它会将多出来的任务放入任务队列中,处理完别的任务再从任务队列中取出并处理。
threadFactory ,线程工厂。创建线程用的工厂类。
handler ,策略。其实是拒绝处理任务时的策略。直译过来就是当达到线程边界和任务队列满了的时候,导致的线程阻塞时,要执行的策略。共四种策略,默认策略为 AbortPolicy。
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
通过如上7个参数,即可自定义出自己想要的线程池。ThreadPoolExecutor类继承了AbstractExecutorService类,AbstractExecutorService类实现了ExecutorService接口。
Executors
网传另外一种创建线程的方式就是Executors ,Executors 创建线程池共有6种方式,创建了6种不同类型的线程池,我们来逐个看下它们的源码实现。
FixedThreadPool
FixedThreadPool,固定线程池。它是创建一个固定数量的线程池,Executors中有两个实现的方法。
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}
从中可以看出,所谓固定线程池,是调用new ThreadPoolExecutor创建一个核心线程数=最大线程数=固定值的这么一个线程池。这种线程池的特性,自然也就是核心线程数=最大线程数,并且线程在空闲时不会被回收。
CachedThreadPool
CachedThreadPool,缓存线程池。实现有如下两种:
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,依然是调用new ThreadPoolExecutor,创建一个核心线程数为0,最大线程数为MAX_VALUE = 0x7fffffff的线程池。这样由于没有核心线程,所有的线程在没有任务时均会死亡。
SingleThreadExecutor
SingleThreadExecutor,单线程线程池。顾名思义,只有一个线程的线程池。源码如下:
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));}
如源码所示,核心线程数=最大线程数=1,依然是通过new ThreadPoolExecutor创建的线程池。这个就是所谓的单线程线程池。
ScheduledThreadPool
ScheduledThreadPool,定时线程池。它可以定时或者延时的执行任务。原文描述如下:
两种实现源码如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
可以看出,通过调用new ScheduledThreadPoolExecutor指定核心线程数或者核心线程数和线程工厂完成构造线程池。而所谓的ScheduledThreadPoolExecutor,其构造函数通过super构造的ThreadPoolExecutor,也就是说,本质上还是ThreadPoolExecutor来构造的。ScheduledThreadPoolExecutor源码如下:
public class ScheduledThreadPoolExecutorextends ThreadPoolExecutorimplements ScheduledExecutorService
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}
其定时或者说延时功能看起来是通过将任务队列指定为延迟队列来实现的。`
SingleThreadScheduledExecutor
SingleThreadScheduledExecutor,单线程定时线程池。在Excutors中构造函数实现如下
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1, threadFactory));
}
所谓的DelegatedScheduledExecutorService,其实是Excutors的静态内部类,源码如下:
static class DelegatedScheduledExecutorServiceextends DelegatedExecutorServiceimplements ScheduledExecutorService {private final ScheduledExecutorService e;DelegatedScheduledExecutorService(ScheduledExecutorService executor) {super(executor);e = executor;}
DelegatedScheduledExecutorService的父类DelegatedExecutorService,也是一个静态内部类。源码如下:
/*** A wrapper class that exposes only the ExecutorService methods* of an ExecutorService implementation.*/static class DelegatedExecutorService extends AbstractExecutorService {private final ExecutorService e;DelegatedExecutorService(ExecutorService executor) { e = executor; }
从中可以看出,所谓的单线程定时线程池,是首先调用定时线程池里用到的new ScheduledThreadPoolExecutor,构造出以后,将其丢入DelegatedScheduledExecutorService的构造函数中,DelegatedScheduledExecutorService调用其父类DelegatedExecutorService的构造函数,也就是将DelegatedExecutorService中的ExecutorService e变量指定为传入的ScheduledThreadPoolExecutor(ScheduledThreadPoolExecutor实现了接口ExecutorService )。
不过目前还没理解的是为啥不能直接返回new ScheduledThreadPoolExecutor(1),绕了这么一大圈的意义何在呢。。这个后面再思考思考。。。
WorkStealingPool
WorkStealingPool,抢占式线程池。源码如下:
public static ExecutorService newWorkStealingPool(int parallelism) {return new ForkJoinPool(parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);
}
public static ExecutorService newWorkStealingPool() {return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);}
它与前面几个线程池不同的地方在于,它的任务的完成,不是按照workQueue先进先出的顺序来的,它是抢占式的。ForkJoinPool也并非像ScheduledThreadPoolExecutor继承了ThreadPoolExecutor。代码中没有调用ThreadPoolExecutor。
ForkJoinPool是直接继承的AbstractExecutorService。源码如下:
public class ForkJoinPool extends AbstractExecutorService
ForkJoinPool
由上一部分引出了另一种创建线程池的方式,也就是ForkJoinPool。它是一种基于Fork/Join思想的线程池,思想类似MapReduce。不过由于它需要对任务进行拆分,线程的任务会被另一个线程偷走,导致线程的界限不是很明确,一般来说只有计算密集型任务才会需要它。这个ForkJoinPool后续再聊。
总结
经过源码的探究,可以看出,线程池的构造,可以使用new ThreadPoolExecutor来直接构造一个自定义的线程池,或者Executors的方法来构造6种不同的线程池。Executors中的6种方法,其中有5种都是调用new ThreadPoolExecutor来构造线程池,只有抢占式线程池WorkStealingPool是没有调用new ThreadPoolExecutor来实现线程池,而是通过源自JDK1.7的ForkJoinPool来构建线程池。
也就是说,不管用ThreadPoolExecutor还是Executors,大多数底层都是调用new ThreadPoolExecutor(),通过指定上面说的核心线程数、最大线程数、任务队列等来实现不同的线程池功能。只有抢占式线程池WorkStealingPool不是这样的。它的底层是通过ForkJoinPool来构建的线程池。底层的实现,只有ThreadPoolExecutor和ForkJoinPool
参考资料
[1],面试官:线程池有哪几种创建方式,能详细的说下么?
【多线程】创建线程池有几种方式相关推荐
- 线程池的五种状态及创建线程池的几种方式
上篇<Java线程的6种状态详解及创建线程的4种方式> 前言:我们都知道,线程是稀有资源,系统频繁创建会很大程度上影响服务器的使用效率,如果不加以限制,很容易就会把服务器资源耗尽.所以,我 ...
- 创建线程池的七种方式
在 Java 语言中,并发编程往往都是通过床架线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景.总结来说线程池的创建可以分为两大类: 通过 Executors ...
- java创建线程池几种方式_java知识总结-创建线程池的6种方式
一.创建线程池的6种方式: Executors.newCachedThreadPool(); 创建一个可缓存线程池,应用中存在的线程数可以无限大 Executors.newFixedThreadPoo ...
- 从源码分析创建线程池的4种方式
摘要:从创建线程池的源码来深入分析究竟有哪些方式可以创建线程池. 本文分享自华为云社区<[高并发]从源码角度分析创建线程池究竟有哪些方式>,作者:冰 河 . 在Java的高并发领域,线程池 ...
- Java创建线程池的几种方式
方式一:继承Thread类 新建一个类并该类声明为Thread的子类. 这个子类应该重写run类的方法.例如,计算大于规定值的素数的线程可以写成如下: class PrimeThread extend ...
- 创建线程池的四种方式_创建线程到底有几种方式?
很多时候,在项目中使用线程的情况很少,导致很多人只停想起最常见的两种创建线程的方法,即继承Thread类和实现Runnable接口. 而网络上大家有人认为是三种实现方式,也有人认为是四种实现,下面我们 ...
- 线程池介绍及创建线程池的4种方式
1. 什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池.在开发过程中,合理地使用线程池能够带来3个好处. 第一:降低资源消耗.通过重复利 ...
- JAVA中创建线程池的五种方法及比较
之前写过JAVA中创建线程的三种方法及比较.这次来说说线程池. JAVA中创建线程池主要有两类方法,一类是通过Executors工厂类提供的方法,该类提供了4种不同的线程池可供使用.另一类是通过Thr ...
- 创建线程的第三种方式:实现Callable接口(含部分源码解析)
创建线程的第三种方式--实现Callable接口 package com.lqy.Multithreading; import java.util.concurrent.Callable; impor ...
最新文章
- 致谢 开源开发者的贡献_对开源做出的贡献如何使我成为更好的开发人员,以及如何做到这一点...
- 通过Spring配置文件中bean中的property赋值
- 蓝牙通话之HFP协议
- 十进制转换成二进制列表
- VB winform自动更新 笔记
- precision recall
- vxworks的default boot line说明
- esper(4-3)-Non-Overlapping Context
- easyui中idField的作用
- HTML_简单JQ的AJAX响应式交互
- vue 第三天(绑定属性)
- 程序员的修炼之道 从小工到小工
- 电影《阿凡达》观后感
- Strurts(四)——从Struts原型模拟看大道至简(含实例下载)
- 【C语言】编程初学者入门训练(7)
- 修改hosts不管用 为什么修改127 0 0 1指向的域名,访问域名却弹出别的网站
- HTML页面刷新方法
- Document.location
- GLAMD: Global and Local Attention Mask Distillation for Object Detectors
- bzoj4399 魔法少女LJJ 线段树合并