Java并发编程(线程池)

Java并发编程(线程池)

  1. 线程频繁的创建和销毁带来性能开销
  2. 线程的数量过多,上下文切换,消耗CPU资源

池化技术

连接池、对象池、内存池、线程池

池化计数的核心:复用

线程池

提前创建一系列的线程,保存在这个线程池中。
有任务要执行时,从线程池取出线程,任务执行完后归还线程。

Java线程池

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
      ...
    }
  • 线程池参数:

    • corePoolSize:核心线程数
    • maximumPoolSize:允许创建的最大线程数量
    • keepAliveTime:空闲线程的存活时间
    • unit:keepAliveTime 的时间单位
    • workQueue:工作队列,存放待执行任务的队列。
    • threadFactory:创建线程的工厂,可以自定义线程名等属性
    • handler:拒绝策略
      • AbortPolicy:抛出RejectedExecutionException异常。(默认的拒绝策略)
      • DiscardPolicy:丢弃该任务,不会有任何异常抛出。
      • DiscardOldestPolicy:丢弃队列中最前面的一个任务,并尝试再次提交被拒绝的任务。
      • CallerRunsPolicy:由提交任务的线程来执行该任务。
  • 线程池运行过程:

    • 当提交一个新任务时,如果当前线程数小于corePoolSize,那么会创建新的线程执行任务。
    • 当运行的任务数达到corePoolSize后,后续提交的任务会存放在工作队列,任务调度时再从队列中取出任务执行。
    • 当提交一个新任务,如果当前线程数大于等于corePoolSize,小于maximumPoolSize,且工作队列已满时,会创建新的线程执行任务。
    • 当线程池线程数达到maximumPoolSize,且工作队列达到上限,提交的任务使用拒绝策略处理。
  • 空闲线程的回收:

    • 当线程数大于核心线程数时,空闲时间超过keepAliveTime的线程会被回收。
    • 如果设置了allowCoreThreadTimeout=true,当线程数小于等于核心线程数时,也会回收空闲的线程。

Java中提供的线程池

  • newFixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • newSingleThreadExecutor
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • newCachedThreadPool
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • newScheduledThreadPool
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

线程池的设计

在这里插入图片描述

线程池的线程数量设置

  • 线程池线程数量公式

在这里插入图片描述

  • CPU密集性

    • CPU的利用率高,尽可能避免发生线程上下文的切换
    • W很小,C很大
    • 线程数设定参考:CPU核数 + 1
  • IO密集型

    • CPU的利用率不高,等待IO时间
    • W很大,C很小
    • 线程数设定参考:根据公式计算(关注W/C的比值),或者2*CPU核数
  • 公式只是理论值,实际环境中服务器上有很多其他线程在运行,都会占用资源。要靠压测来验证。

  • 压测方法:

    • 尽可能排除其他线程的干扰
    • 设定目标:
      • 期望的CPU利用率
      • 期望的GC指标:GC频率/吞吐量/停顿时间
      • 期望的TPS/QPS,RT平均响应时间
    • 不断调整线程数来压测,满足自己的目标期望。
  • 压测后得到多个线程数的数值和对应的性能指标

    • 对于核心应用,业务调用频繁,可以设置核心线程数=最大线程数
    • 对于非核心应用,可以设置核心线程数 < 最大线程数

考虑资源瓶颈

  • 真实的调用场景往往会很复杂,关于优化性能,要考虑到整个调用链路上的所有节点,哪些是资源瓶颈,优先针对资源瓶颈优化。
  • 比如说做一个文件上传功能,带宽是2M,上传的文件是1M,那么每秒最多能上传2个文件,限制了整体的吞吐量,设置过多的线程处理也无法提高性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值