文档章节

Java线程池的合理使用

FEINIK
 FEINIK
发布于 2017/08/08 09:55
字数 1569
阅读 134
收藏 0

1、概述

大家都知道线程的创建是需要消耗系统有限的资源的,如果不加限制的创建线程那么最终会拖垮整个服务,好的一点是我们可以通过线程池的方式来解决,线程池为线程生命周期开销问题和资源不足问题提供了解决方案。

2、什么是线程池

就是由多个线程组成的一个集合体,将需要执行的任务交给这个集合体,它会负责从集合体中拿出一个线程去执行任务,执行完后该线程又返回集合体等待下个任务的到来,可以看出这些线程可以被重复利用,这可以极大的减少线程的重复创建与销毁所带来的系统开销。

线程池内部主要是由工作线程与任务队列组成的,工作线程会不断循环从任务队列中取出任务然后执行,任务队列是用来存放待执行的任务。

3、线程池的创建

3.1、方式一

通过java包java.util.concurrent中的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,
                          RejectedExecutionHandler handler)

主要参数解释:

  • corePoolSize:线程池中的核心线程个数,当每次有新任务到来时,线程池都会创建一个新的线程,直到线程池中的线程数目大于等于corePoolSize时,接下来的任务会加入任务队列,如果任务队列已满,则会创建新的线程,直到达到maximumPoolSize。
  • maximumPoolSize:线程池中的最大线程个数
  • keepAliveTime:线程池中除核心线程之外的其他线程的存活时间,也就是说一个非核心线程,在空闲等待新任务时的最长等待时间,如果到了时间还没有新任务可执行,就会被终止。如果该值为0,表示所有非核心线程都不会超时终止。
  • workQueue:任务队列
  • threadFactory:表示对创建的线程进行一些线程初始设置
  • handler:表示任务的拒绝处理策略,拒绝策略需要在任务队列有界,且maximumPoolSize有限的情况下才会触发

3.2、方式二

通过java包java.util.concurrent中的Executors工厂类来创建,Executors类提供了一些用于方便创建线程池的静态工厂方法:

public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newWorkStealingPool()

 

解释:

(1)newCachedThreadPool() 60秒超时时间的缓存线程池,内部创建线程池的的方式为:

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                              60L, TimeUnit.SECONDS,
                              new SynchronousQueue<Runnable>());

注:该线程池的核心线程数为0,可创建的最大线程数无限制,线程的存活时间为60秒,任务队列使用的是 SynchronousQueue(同步阻塞队列)

优点:该线程池适用于系统负载低,且任务执行时间短的场景,因为任务无需排队,所以执行效率会更高

缺点:在高负载的场景下会导致创建出大量的线程,最终导致大量的CPU切换带来的开销,以及OOM

(2)newFixedThreadPool(int nThreads) 可指定线程数目的线程池,内部创建线程池的方式为:

return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());

注:该线程池的核心线程数与最大线程数都为用户指定的nThreads,创建的线程无超时时间,任务队列采用的是LinkedBlockingQueue(无界队列)

优点:创建的线程数目可控,通过无界队列可以保证新任务的排队执行

缺点:由于队列无界,如果系统负载较大,那么有可能导致队列中会积压大量任务,最终导致OOM

(3)newSingleThreadExecutor() 内部创建线程池的方式为:

return new ThreadPoolExecutor(1, 1,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>())

注:只有一个线程的线程池,且该线程无超时时间,任务队列采用的是LinkedBlockingQueue(无界阻塞队列)

优点:由于线程池中只有一个线程,所以队列中的任务会被顺序执行,适合需要顺序执行任务的场景

缺点:由于队列无界,如果系统负载较大,那么有可能导致队列中会积压大量任务,最终导致OOM

(4)newScheduledThreadPool(int corePoolSize) 执行定时任务的线程池,内部创建线程池的方式为:

super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
      new DelayedWorkQueue());

注:用户可指定核心线程数corePoolSize,最大线程数无限制,线程池中的线程无超时时间,任务队列采用的是DelayedWorkQueue(延时阻塞工作队列), 只有队列中的任务的延时时间到达时,才会被线程池中的某个线程取走执行,适合需要延时执行任务的场景。

(5)newWorkStealingPool() 可以并行执行任务的线程池,jdk1.8开始支持,线程池中的线程数量为所在机器CPU的核数

并发:一般指多个线程在同一时刻同时执行一个任务,如多个用户(线程)在同一时刻同时进行网上支付操作

并行:一般指将一个大的计算密集型的任务分解成多个小任务,然后利用CPU多核的优势来分别一起执行这些小任务,最后在汇总小任务的结果(如JDK8支持对数组并行排序,或并行查找最大值,最小值),现在比较流行的Fork/Join模式就是采用的并行的编程方式

4、总结

本文主要介绍了两种创建线程池的方式,第一种创建方式更接近于底层的创建方式,相较于第二种方式稍微复杂一点,但是第一种方式更加方便我们根据不同的应用配置出合理的线程池,所以推荐使用第一种方式来创建线程池,使用第二种方式要小心,如果系统负载比较大,且任务比较耗时的情况下可能会导致OOM问题。

 

© 著作权归作者所有

FEINIK
粉丝 227
博文 61
码字总数 61705
作品 0
广州
高级程序员
私信 提问
Java多线程之线程池(ThreadPoolExecutor)实现原理分析(一)

在上一篇文章Java中实现多线程的3种方法介绍和比较中,我们讲解了Java中实现多线程的3种方法。使用多线程,就必须要考虑使用线程池,今天我们来聊聊线程池的那些事。 注:源码都是基于JDK1....

小怪聊职场
2018/05/14
0
0
Java并发编程之ThreadLocal内存泄漏探究

使用 ThreadLocal 不当可能会导致内存泄露,是什么原因导致的内存泄漏呢? 我们首先看一个例子,代码如下: / Created by cong on 2018/7/14. */public class ThreadLocalOutOfMemoryTest { ...

狂小白
2018/07/14
0
0
进一步理解Java中的线程(下)

要想真正的理解Java并发编程,线程是无论如何都必须要彻底理解的一个重要概念。那么,在开始深入介绍之前,我们先来深入的学习一下线程。前面一个章节中已经介绍过线程的一些基本知识,包括线...

HollisChuang's Blog
2018/12/22
0
0
Java虚拟机监控指标及监控配置

版权声明:本文为博主原创文章,未经博主允许不得转载。欢迎访问我的博客 https://blog.csdn.net/smooth00/article/details/70229653 主要是对Java虚拟机(JVM)的远程监视,如jdk自带的工具j...

smooth00
2017/04/28
0
0
ThreadLocal 内存泄露相关注意事项

凡事有两面性,知道其优势和劣势才是我们选择使用与否的判断依据 1、优势 A:ThreadLocal 可以使得线程独有的局部变量,在整个线程存活期间内跨越类和实例的进行使用,等同于为线程内多个实例...

重城重楼
04/10
46
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux 之docker部署,走过的那些坑

初次使用docker, 多有不足,这里把坑一点点的记下来 概念篇 docker 是什么? 太多文字不同,书读的少, 不懂大大道理; 在我的理解, 它就是个沙箱环境; 在linux中 ,独立进程, 有着自己的小世界 使...

莫库什勒
31分钟前
4
0
vue class绑定 组件

本文转载于:专业的前端网站➬vue class绑定 组件 当在一个自定义组件上使用 class 属性时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。 例如,如果你声明了这个...

前端老手
今天
4
0
exist和in

exist和in select ..from table where exist (子查询) ; select ..from table where 字段 in (子查询) ; 如果主查询的数据集大,则使用In,效率高。 如果子查询的数据集大,则使用exist,效率高...

潦草的犀牛
今天
5
0
Android OkHttp + Retrofit 取消请求的方法

本文链接 前言 在某一个界面,用户发起了一个网络请求,因为某种原因用户在网络请求完成前离开了当前界面,比较好的做法是取消这个网络请求。对于OkHttp来说,具体是调用Call的cancel方法。 ...

shzwork
今天
6
0
并发编程之Callable异步,Future模式

Callable 在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或...

codeobj
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部