java 线程池

1 背景

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程也属于宝贵的系统资源。

2 解决方法

在Java中可以通过线程池来避免频繁创建销毁线程、实现重复利用,其实际上就是容纳了多个线程的容器。这种思想是不是非常类似于我们生活中的共享单车呢?

3 线程池优势

  1. 降低资源消耗。减少了创建和销毁线程的次数。
  2. 提高响应速度。任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而导致服务器挂掉。

4 java中的线程池

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService

要配置一个线程池是比较复杂的,因此在java.util.concurrent.Executors线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。

Executors类中常用的创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):创建的是有界线程池,也就是池中的线程个数可以指定最大数量。
  • newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。
  • newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于调用newFixedThreadPool()方法时传入参数为1。
  • newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。
  • newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。
  • ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争。
  • ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。如果当前机器有4个CPU,则目标并行级别被设置为4,也就是相当于为前一个方法传入4作为参数。

获取到线程池ExecutorService 对象后,调用如下方法使用线程池对象:

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行(Future接口:用来记录线程任务执行完毕后产生的结果。)

  • <T> Future<T> submit(Callable<T> task) : 获取线程池中的某一个线程对象,并执行(Future : 表示计算的结果)

  • V get() : 获取计算完成的结果。

使用线程池中线程对象的步骤:

  1. 创建线程池对象 Executors.newFixedThreadPool()
  2. 创建Runnable接口子类对象 new MyRunnable() 或 new MyCallable() 。(task)
  3. 提交Runnable接口子类对象 submit()。(take task)
  4. 关闭线程池 shutdown() 或 shutdownNow() (前者使线程池状态由RUNNING变为SHUTDOWN,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务;后者使线程池状态由RUNNING变为STOP,不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程)。

线程池的工作流程:
请添加图片描述

线程池中的重要参数:

  • corePoolSize(核心工作线程数):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时。
  • maximumPoolSize(最大线程数):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
  • keepAliveTime(多余线程存活时间):当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
  • workQueue(队列):用于传输和保存等待执行任务的阻塞队列。
  • threadFactory(线程创建工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
  • handler(拒绝策略):当线程池和队列都满了,再加入线程会执行此策略

线程池的拒绝策略:
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • DiscardPolicy:也是丢弃任务,但是不抛出异常。
  • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复该过程)。
  • CallerRunsPolicy:由调用线程处理该任务。

线程池的状态:
一共有五种状态, 分别是:

  • RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务。
  • SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。
  • STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNINGSHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态。
  • TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
  • TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法是空实现,可以重写该方法进行相应的处理。

下图为线程池的状态转换过程
请添加图片描述
线程池的队列大小通常怎么设置?

  1. CPU密集型任务
    尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

  2. IO密集型任务:
    可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

  3. 混合型任务
    可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰茶不冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值