共计 3592 个字符,预计需要花费 9 分钟才能阅读完成。
线程池根本介绍与应用
咱们晓得,在 Java 中,创建对象,仅仅是在 JVM 的堆里调配一块内存而已;而创立一个线程,却须要调用操作系统内核的 API,而后操作系统要为线程调配一系列的资源,这个老本就很高了,所以线程是一个重量级的对象,应该防止频繁创立和销毁。
所以 Java 中提供了线程池,其本质就是一个包容多个线程的容器,其中的线程能够重复应用,省去了频繁创立线程对象的操作,无需重复创立线程而耗费过多资源。
JDK 线程池相干类
JDK 中提供的无关线程池的相干类及其关系如下图:
- Executor 接口:线程池的形象接口,只蕴含一个 execute 办法。
- ExecutorService 子接口:提供了无关终止线程池和 Future 返回值的一些办法。
- AbstractExecutorService 抽象类:提供了 ExecutorService 的一些默认实现。
- ThreadPoolExecutor 类:JDK 提供的线程池的实现类。
- Executors 类:线程池工厂类,提供了几种线程池的工厂办法。
上面次要介绍 JDK 提供的线程池实现类 – ThreadPoolExecutor 类。
ThreadPoolExecutor 类
Java 提供的线程池相干的工具类中,最外围的是 ThreadPoolExecutor。
构造方法
ThreadPoolExecutor 的构造函数非常复杂,如上面代码所示,这个最齐备的构造函数有 7 个参数。
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
上面一一介绍下参数的含意。
corePoolSize
- 默认状况下,在创立了线程池后,线程池中的线程数为 0,当有工作来之后,才会去创立一个线程来执行工作
- 当线程池中的线程数目达到 corePoolSize 后,就进行线程创立,转而会把工作放到工作队列当中期待。
- 调用 prestartAllCoreThreads() 或者 prestartCoreThread() 办法,能够预创立线程,即在没有工作到来之前就创立 corePoolSize 个线程或者一个线程
maxPoolSize
- 当线程数大于或等于外围线程,且工作队列已满时,线程池会创立新的线程,直到线程数量达到 maxPoolSize。
- 如果线程数已等于 maxPoolSize,且工作队列已满,则已超出线程池的解决能力,线程池会回绝解决工作而抛出异样。
keepAliveTime & unit
当线程闲暇工夫达到 keepAliveTime,单位 unit 时,该线程会退出,直到线程数量等于 corePoolSize。
workQueue
工作队列,一个阻塞队列,用来存储期待执行的工作,倡议 workQueue 不要应用无界队列,尽量应用有界队列。防止大量工作期待,造成 OOM。反对有界的阻塞队列有 ArrayBlockingQueue 和 LinkedBlockingQueue。
threadFactory
线程工厂,通过这个参数你能够自定义如何创立线程,例如你能够给线程指定一个有意义的名字。
handler
回绝策略。如果线程池中所有的线程都在繁忙,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交工作,线程池就会回绝接管,能够通过 handler 这个参数来指定回绝的策略。ThreadPoolExecutor 曾经提供了以下 4 种策略:
- ThreadPoolExecutor.AbortPolicy:默认的回绝策略。抛弃工作并抛出 RejectedExecutionException 异样。
- ThreadPoolExecutor.DiscardPolicy:抛弃工作,然而不抛出异样。
- ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列期待最久的工作,而后从新尝试执行工作(反复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:间接在 execute 办法的调用线程中运行被回绝的工作。
罕用办法
提交工作办法
void execute(Runnable command):提交工作给线程池执行,工作无返回值
Future<?> submit(Runnable task):因为 Runnable 接口没有返回值,所以 Future 返回值执行 get() 办法返回值为 null,作用只是期待,相似于 join
<T> Future<T> submit(Runnable task, T result):因为 Runnable 没有返回值,所以额定提供了一个参数,作为返回值。<T> Future<T> submit(Callable<T> task):提交工作给线程池执行,可能返回执行后果。
其余办法
void allowCoreThreadTimeOut(boolean value):是否容许外围线程超时,默认 false。shutdown():敞开线程池,期待工作都执行完
shutdownNow():敞开线程池,不期待工作执行完,并返回期待执行的工作列表。getTaskCount():线程池已执行和未执行的工作总数
getCompletedTaskCount():已实现的工作数量
getPoolSize():线程池以后的线程数量
getActiveCount():以后线程池中正在执行工作的线程数量
线程池工作的执行流程
线程池工作的个别执行流程图如下图所示:
线程池初始化示例
上面是一个线程池初始化的示例,仅供参考
// 初始化示例
private static final ThreadPoolExecutor pool;
static {ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("po-detail-pool-%d").build();
pool = new ThreadPoolExecutor(
4,
8,
60L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(512),
threadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.allowCoreThreadTimeOut(true);
}
初始化参数含意解释:
- threadFactory:给出带业务语义的线程命名。
- corePoolSize:疾速启动 4 个线程解决该业务,是足够的。
- maximumPoolSize:IO 密集型业务,我的服务器是 4C8G 的,所以 4 *2=8。
- keepAliveTime:服务器资源缓和,让闲暇的线程疾速开释。
- pool.allowCoreThreadTimeOut(true):也是为了在能够的时候,让线程开释,开释资源。
- workQueue:一个工作的执行时长在 100~300ms,业务高峰期 8 个线程,依照 10s 超时(曾经很高了)。10s 钟,8 个线程,能够解决 10 1000ms / 200ms 8 = 400 个工作左右,往上再取一点,512 曾经很多了。
- handler:极其状况下,一些工作只能抛弃,爱护服务端。
线程池应用注意事项
- 防止应用 Executors 类创立线程池,会有 OOM 危险。
- 创立线程或线程池时请指定有意义的线程名称,不便出错时回溯。即 threadFactory 参数要结构好。
- 倡议不同类别的业务用不同的线程池,至于线程池的数量,各自计算各自的,而后去做压测。
- workQueue 不要应用无界队列,尽量应用有界队列。防止大量工作期待,造成 OOM。反对有界的阻塞队列有 ArrayBlockingQueue 和 LinkedBlockingQueue。
- 如果是资源缓和的利用,应用 allowsCoreThreadTimeOut 能够进步资源利用率。
- 尽管应用线程池有多种异样解决的形式,但在工作代码中,应用 try-catch 最通用,也能给不同工作的异样解决做精细化。
- 线程池默认的回绝策略会 throw RejectedExecutionException 这是个运行时异样,对于运行时异样编译器并不强制 catch 它,所以开发人员很容易疏忽。因而默认回绝策略要谨慎应用。如果线程池解决的工作十分重要,倡议自定义本人的回绝策略;并且在理论工作中,自定义的回绝策略往往和降级策略配合应用。
- CPU 密集型工作,最大线程数初始值能够配置 N +1。I/ O 密集型工作,最大线程数初始值能够配置 2N。之后再能够依据压测来调整。
参考资料
- 10 问 10 答:你真的理解线程池吗?
- 图解 | 你管这破玩意叫线程池?
- Executor 与线程池:如何创立正确的线程池?- 极客工夫