Java线程池的合理使用

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问题。

 

转载于:https://my.oschina.net/feinik/blog/1504330

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值