zoukankan      html  css  js  c++  java
  • Java并发编程实战 第8章 线程池的使用

    合理的控制线程池的大小:

    下面内容来自网络。不过跟作者说的一致。不想自己敲了。留个记录。

    要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:

    任务的性质:CPU密集型任务、IO密集型任务、混合型任务。

    任务的优先级:高、中、低。

    任务的执行时间:长、中、短。

    任务的依赖性:是否依赖其他系统资源,如数据库连接等。

    性质不同的任务可以交给不同规模的线程池执行。

    对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。

    若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。 
    当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。

    在这篇如何合理地估算线程池大小?文章中发现了一个估算合理值的公式

    最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

    比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

    最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

    可以得出一个结论: 
    线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。 
    以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。

    并发编程网上的一个问题 
    高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池? 
    (1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换 
    (2)并发不高、任务执行时间长的业务要区分开看: 
      a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务 
      b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换 
    (3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

     

    我们可以通过如下代码来获取处理器的数量:

    //获取处理器的数量

    System.out.println(Runtime.getRuntime().availableProcessors());

    管理队列任务

    ThreadPoolExecutor是ExecutorService的实现类。我们使用Executors.newCacheThreadPoo这些方法返回的对象就是已经定制好的ThreadPoolExecutor对象。

    ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。

    基本的任务排队方法有三种:

    • 有界队列
    • 无界队列
    • 直接提交

    为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展挂钩。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导(这里只罗列了部分我关注的 API中有更多):

    • 排队

    所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

    排队有三种通用策略:

    直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。如:Executors.newCachedThreadPool()就是使用的这个。

    无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()就是使用的这个。

    有界队列。有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 如果有界队列慢了,新的任务来了,就会根据预先设计好的饱和策略来处理。

    • 挂钩方法

    此类提供 protected 可重写beforeExecute(java.lang.Thread, java.lang.Runnable)afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。

    如果挂钩或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。

    • 队列维护

    方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。remove(java.lang.Runnable)purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。

    配置通过本标准方法返回的ThreadPoolExecutor

    1. public static void main(String[] args) {
    2.        ExecutorService es = Executors.newCachedThreadPool();
    3.        System.out.println(es instanceof ThreadPoolExecutor);
    4.        ThreadPoolExecutor tpe = (ThreadPoolExecutor)es;
    5.        tpe.setKeepAliveTime(1, TimeUnit.SECONDS);
    6.    }

    上面是ThreadPoolExecutor的一个用途,其实可以完全自定义自己的ThreadPoolExecutor,不使用Executors.new生成的逻辑。事实上,Executors.new生成的就是配置好的ThreadPoolExecutor。

  • 相关阅读:
    ssm框架配置文件
    接口调用post请求参数在body中
    mysql三种连接方式
    jwt认证登录
    JWT工具类
    token的创建及解析
    IIS目录
    C# 增加多个分部类
    计算机知识
    Kibana 的安装(Windows版本)
  • 原文地址:https://www.cnblogs.com/xiaolang8762400/p/7067999.html
Copyright © 2011-2022 走看看