Java线程池相关

Posted 胖虎不会写代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程池相关相关的知识,希望对你有一定的参考价值。

建议阅读原文以获得最好的阅读体验


前言

线程是我们再熟悉不过的东西,平常也会经常用到。但最近在仔细研究《阿里巴巴Java开发手册》后,发现我在线程的使用上还是有些误区。网络上的不少文章和开源代码也是不规范的,于是在此做个总结。

创建与运行线程

创建与运行线程我们都很熟悉,这里就不废话了,无非就是继承 Thread类或实现 Runnable接口并重写 run方法,然后调用 start方法来启动线程。写成匿名类的方式如下:

 
   
   
 
  1. new Thread(new Runnable()

  2. {

  3.    @Override

  4.    public void run()

  5.    {

  6.        // 线程内容

  7.    }

  8. }).start();

但稍有常识的人都知道,这种野鸡线程写法在工程中是明令禁止的。这种写法在创建线程时会大量消耗系统资源,在性能上也非常慢。而且野鸡线程会造成代码混乱,降低可读性,难以维护。所以我们一般使用线程池来管理线程。

线程池

Executor是 Java里线程池的顶级接口, Executor提供了一些静态的工厂方法,生成四种常见的线程池:

  • newCachedThreadPool 创建一个可缓存的线程池。会将线程重复利用,如无线程可用,则新建线程。并会回收长时间(60秒)未使用的线程,在执行多个短期异步程序时,这个线程池可以提高性能。此线程池的最大线程数量依赖于操作系统或JVM支持的最大线程数。

  • newFixedThreadPool 创建一个可重用的定长线程池,每次提交任务都会新建线程直到数量限制,后面的线程会一直复用这些线程,如有线程异常结束,会马上新建线程补充。以无界队列方式运行,超出数量限制的线程会在队列中等待可用线程。

  • newScheduledThreadPool 创建一个线程池,支持定时及周期性运行。

  • newSingleThreadExecutor 创建一个只有单个工作线程的线程池,以无界队列方式运行,当工作线程出现异常,会新建线程替换。可保证顺序地执行各个任务。和 newFixedThreadPool(1)运行上没有差别(网上很多文章都说有差别。但经我翻文档,实验,阅读源码后并没有发现运行机制的差别,网上的这些文章也都是互相复制的,作者本人都没有实验过,非常不负责)。

使用示例:

 
   
   
 
  1. ExecutorService executorService = Executors.newFixedThreadPool(100);

  2. executorService.execute(new Runnable()

  3. {

  4.    @Override

  5.    public void run()

  6.    {

  7.        // 线程内容

  8.    }

  9. });

只有 newScheduledThreadPool比较特殊:

 
   
   
 
  1. ExecutorService service = Executors.newScheduledThreadPool(100);

  2. service.schedule(new Runnable()

  3. {

  4.    @Override

  5.    public void run()

  6.    {

  7.        // 线程内容

  8.    }

  9. }, 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有四个构造方法,分别是:

 
   
   
 
  1. public ThreadPoolExecutor(int corePoolSize,

  2.                          int maximumPoolSize,

  3.                          long keepAliveTime,

  4.                          TimeUnit unit,

  5.                          BlockingQueue<Runnable> workQueue)

 
   
   
 
  1. public ThreadPoolExecutor(int corePoolSize,

  2.                          int maximumPoolSize,

  3.                          long keepAliveTime,

  4.                          TimeUnit unit,

  5.                          BlockingQueue<Runnable> workQueue,

  6.                          ThreadFactory threadFactory)

 
   
   
 
  1. public ThreadPoolExecutor(int corePoolSize,

  2.                          int maximumPoolSize,

  3.                          long keepAliveTime,

  4.                          TimeUnit unit,

  5.                          BlockingQueue<Runnable> workQueue,

  6.                          RejectedExecutionHandler handler)

 
   
   
 
  1. public ThreadPoolExecutor(int corePoolSize,

  2.                          int maximumPoolSize,

  3.                          long keepAliveTime,

  4.                          TimeUnit unit,

  5.                          BlockingQueue<Runnable> workQueue,

  6.                          ThreadFactory threadFactory,

  7.                          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线程池相关的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池详解

Java 线程池详解

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

Java线程池详解

IDEA对新建java线程池的建议

Java线程池相关