Java线程池相关
Posted 胖虎不会写代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程池相关相关的知识,希望对你有一定的参考价值。
建议阅读原文以获得最好的阅读体验
前言
线程是我们再熟悉不过的东西,平常也会经常用到。但最近在仔细研究《阿里巴巴Java开发手册》后,发现我在线程的使用上还是有些误区。网络上的不少文章和开源代码也是不规范的,于是在此做个总结。
创建与运行线程
创建与运行线程我们都很熟悉,这里就不废话了,无非就是继承 Thread
类或实现 Runnable
接口并重写 run
方法,然后调用 start
方法来启动线程。写成匿名类的方式如下:
new Thread(new Runnable()
{
@Override
public void run()
{
// 线程内容
}
}).start();
但稍有常识的人都知道,这种野鸡线程写法在工程中是明令禁止的。这种写法在创建线程时会大量消耗系统资源,在性能上也非常慢。而且野鸡线程会造成代码混乱,降低可读性,难以维护。所以我们一般使用线程池来管理线程。
线程池
Executor
是 Java
里线程池的顶级接口, Executor
提供了一些静态的工厂方法,生成四种常见的线程池:
newCachedThreadPool
创建一个可缓存的线程池。会将线程重复利用,如无线程可用,则新建线程。并会回收长时间(60秒)未使用的线程,在执行多个短期异步程序时,这个线程池可以提高性能。此线程池的最大线程数量依赖于操作系统或JVM支持的最大线程数。newFixedThreadPool
创建一个可重用的定长线程池,每次提交任务都会新建线程直到数量限制,后面的线程会一直复用这些线程,如有线程异常结束,会马上新建线程补充。以无界队列方式运行,超出数量限制的线程会在队列中等待可用线程。newScheduledThreadPool
创建一个线程池,支持定时及周期性运行。newSingleThreadExecutor
创建一个只有单个工作线程的线程池,以无界队列方式运行,当工作线程出现异常,会新建线程替换。可保证顺序地执行各个任务。和newFixedThreadPool(1)
在运行上没有差别(网上很多文章都说有差别。但经我翻文档,实验,阅读源码后并没有发现运行机制的差别,网上的这些文章也都是互相复制的,作者本人都没有实验过,非常不负责)。
使用示例:
ExecutorService executorService = Executors.newFixedThreadPool(100);
executorService.execute(new Runnable()
{
@Override
public void run()
{
// 线程内容
}
});
只有 newScheduledThreadPool
比较特殊:
ExecutorService service = Executors.newScheduledThreadPool(100);
service.schedule(new Runnable()
{
@Override
public void run()
{
// 线程内容
}
}, 10, 5, TimeUnit.SECONDS);
表示延迟10秒后每5秒执行一次,推荐使用 ScheduledExecutorService
替代 Timer
, ScheduledExecutorService
功能更强大也更安全。
我以前也一直都是使用 Executors
来创建线程池使用线程,但是!在《阿里巴巴Java开发手册》第一章第六节第四条中有如下规定:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAXVALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAXVALUE,可能会创建大量的线程,从而导致 OOM
所以,我们应该通过 ThreadPoolExecutor
来实现自己的线程池。
ThreadPoolExecutor
ThreadPoolExecutor
是线程池的真正实现,上面提到的四个线程池的创建方法内部也是通过 ThreadPoolExecutor
设置参数来实现的。
构造方法
ThreadPoolExecutor
有四个构造方法,分别是:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明
int corePoolSize 核心线程数量。会一直存活,这些线程即使在空闲状态,也会被保留在线程池中,除非设置了
allowCoreThreadTimeOut
为true
。int maximumPoolSize 线程池中允许的最大线程数量,不允许小于
corePoolSize
。long keepAliveTime 当线程数大于核心线程数时,这是非核心线程的闲置超时时间。
TimeUnit unit
keepAliveTime
参数的时间单位,例如为TimeUnit.SECONDS
时,单位为秒。BlockingQueue<runnable> workqueue 在执行任务之前用于保留任务的队列。该队列将仅保存线程的
Runnable
任务,常见有三种队列:SynchronousQueue
,LinkedBlockingDeque
和ArrayBlockingQueue
。ThreadFactory threadFactory 线程工厂,提供创建新线程的功能。《阿里巴巴Java开发手册》规定要使用带有这个参数的构造方法,来方便地管理线程。
RejectedExecutionHandler handler 当到达线程池资源上限时,添加新线程会调用
RejectedExecutionHandler
接口的rejectedExecution
方法
线程池规则
线程池的规则和 workQueue
的类型有很大关系,当一个线程任务通过 execute
添加到线程池时:
如果线程池中的线程数量 <=
corePoolSize
,则新建线程处理该任务。如果线程池中的线程数量 >
corePoolSize
,<=maximumPoolSize
时。队列为
LinkedBlockingDeque
,则放入队列排队。如果队列已满,则直接新建线程执行任务。队列为
SynchronousQueue
,则直接新建线程处理该任务,这些线程闲置时间超过keepAliveTime
后就会被销毁。如果线程池中的线程数量 >
corePoolSize
和maximumPoolSize
。队列为
LinkedBlockingDeque
,如果队列没有大小限制,会将任务放进队列。也就是当队列LinkedBlockingDeque
并且没有大小限制时,最大线程数maximumPoolSize
是无效的。如果队列有大小限制,则交给handler
拒绝任务。队列为
SynchronousQueue
,会使用handler
拒绝任务。
参考资料
JDK8官方文档
《阿里巴巴Java开发手册》
以上是关于Java线程池相关的主要内容,如果未能解决你的问题,请参考以下文章
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段