当前位置: 首页 > article >正文

JAVA并发编程系列(11)线程池底层原理架构剖析

面试官:说说JAVA线程池的几个核心参数?

之前我们用了10篇文章详细剖析了synchronized、volatile、CAS、AQS、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier、并发锁、Condition等各个核心基础原理,今天开始我们说说并发领域的各种工具包还有应用场景。

1、为什么要用线程池?

日常频繁创建和销毁线程是非常消耗系统资源的操作,会降低系统的整体性能。而线程池通过池化的管理思想,把线程的创建、任务调度、任务执行、任务等待、任务拒绝、销毁等全生命周期各个环节都进行统一管理,大幅提升系统性能,提高线程利用率以及任务响应。

2、线程池核心参数意义

一共有6个:

    
    private volatile ThreadFactory threadFactory;
  
    private volatile RejectedExecutionHandler handler;
    
    private volatile long keepAliveTime;//非核心线程空闲超时时间

    private volatile boolean allowCoreThreadTimeOut;

    private volatile int corePoolSize;//核心线程数量

    private volatile int maximumPoolSize;//最大线程数量
2.1 corePoolSize 核心线程数量

核心线程即使空闲,也不会被回收。如果当前线程数量小于corePoolSize,即使其他核心线程空闲,线程池也会新增创建一个线程来执行任务。

核心线程会一直存活在线程池里,但是如何设置了线程池的allowCoreThreadTimeOut为true,则核心线程空闲一定时间也会被回收。

private volatile boolean allowCoreThreadTimeOut

2.2 maximumPoolSize最大线程数量

线程池最大线程数量maximumPoolSize = 核心线程数量corePoolSize + 非核心线程数量。

场景A:当线程数量>=核心线程数量,且等待队列未满,将任务加入到等待队列。

场景B:当队列已满,并且线程数量<maximumPoolSize ,就新增非核心线程。这种非核心线程就是在空闲时间大于keepAliveTime,就会被回收。

场景C:当队列已满,且线程数量大于等于maximumPoolSize,就触发拒绝策略handler拒绝任务。

2.3 keepAliveTime 线程空闲时间

keepAliveTime允许线程的最大的空闲存活时间。如果一个非核心线程在空闲状态下持续超过keepAliveTime了,就会被回收,以及线程池设置allowCoreThreadTimeOut为true,核心线程也会被回收。

2.4 workQueue任务存储队列

用于存储等待执行的任务。主要有三种类型。

第一个,直接提交SynchronousQueue,把任务直接提交给工作线程而不放到等待队列。这个队列不存储元素,newCachedThreadPool使用的就是这种同步移交队列,吞吐量比LinkedBlockingQueue大。

第二个,LinkedBlockingQueue无界队列,队列的最多容量为int的最大值,相当于无限容量。newFixedThreadPool和newSingleThreadPool使用的就是LinkedBlockingQueue,这个吞吐量比ArrayBlockingQueue高。

第三个,ArrayBlockingQueue有界队列,可以有效避免资源耗尽。

除了这三种,还有DelayQueue、PriorityBlockingQueue。

2.5 threadFactory线程工厂

线程工厂,用来创建线程。

2.6 handler拒绝策略

拒绝任务的策略。当线程数量达到最大线程数量maximumPoolSize,以及等待队列workQueue也满了,这时候需要用来拒绝任务提交时的策略handler。策略类型有抛异常、用调用者所在的线程执行任务、丢弃队列中第一个任务执行当前任务、直接丢弃任务)。

策略种类有:

AbortPolicy:直接抛出异常。

DiscardPolicy:丢弃任务,不抛异常。

DiscardOldestPolicy:将等待队列里等待最久的任务丢弃。

CallerRunsPolicy: 哪个线程提交的任务,哪个线程去处理。

4、有几种线程池?

JAVA线程池ThreadPoolExcutor,常见的有这四种线程池。

//初始化一个无限线程的线程池,无需等待队列
Executors.newCachedThreadPool()
//初始化一个支持周期性运行的线程池
Executors.newScheduledThreadPool(10);
//初始化固定数目线程的线程池
Executors.newFixedThreadPool(10);
//初始化仅有一个线程的线程池
Executors.newSingleThreadExecutor();
4.1 Executors.newCachedThreadPool()可缓存线程池

创建一个可缓存的线程池,如果线程数量大于处理任务时,空闲线程被回收;当提交任务增加时,又可以新建线程去处理任务。这种线程池的线程数无限制,corePoolSize数值为0, maximumPoolSize 的数值都是为 Integer.MAX_VALUE。

线程可复用性很高,可以减少频繁创建/销毁线程,减少系统开销。工作队列workQueue选用SynchronousQueue。

4.2 Executors.newScheduledThreadPool()定时调度线程池

也是固定长度的线程池,但是支持以延迟或者定时的方式去执行任务。

4.3 newSingleThreadExecutor()单线程的线程池

一个线程,corePoolSize 和 maximumPoolSize 的数值都是为 1,线程池里只有一个工作线程执行任务。若这个唯一的线程异常出问题了,会新建另一个线程来替代。所有任务在等待队列是FIFO顺序被执行。

4.4 Executors.newFixedThreadPool()固定长度线程池

每次提交任务的时候就会创建一个新的线程,直到达到线程池的最大数量限制,如何任务大于线程数量,就进入等待队列。 corePoolSize 和 maximumPoolSize 的数值相等。工作队列选用LinkedBlockingQueue。

最后,我们上一个线程池demo,固定线程池,每次最多N个线程在执行任务,其他任务等待。

package lading.java.mutithread;

import cn.hutool.core.date.DateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 固定线程池,线程数量为3
 * 提交多个count+1任务计算
 *
 */
public class Demo013ThreadPool {
    //计算任务count
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        //固定数量线程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        Executors.newScheduledThreadPool(10);
        //提交20次,对count+1的任务
        for (int i = 1; i < 20 + 1; i++) {
            service.submit(new Thread(() -> {
                //模拟每次计算耗时2s
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //完成本次对count+1计算任务
                System.out.println(DateTime.now().toString("YYYY-MM-dd hh:mm:ss") + " " +Thread.currentThread().getName() + "线程 执行计算任务:" + count.incrementAndGet());
            }));
        }
        service.shutdown();
    }
}

每次只有三个线程在运行任务,其他任务等之前任务执行完成后再执行。

今天就分享到这,明天分享并发容器CurrentHashMap。


http://www.kler.cn/a/323616.html

相关文章:

  • 电子工牌独立双通道定向拾音方案(有视频演示)
  • ETH挖矿显卡超频信息汇总
  • 什么是项目完整性管理?
  • apk反编译修改教程系列-----apk应用反编译中AndroidManifest.xml详细代码释义解析 包含各种权限 代码含义【二】
  • python selenium库的使用:通过兴趣点获取坐标
  • MDK 5 各个历史版本下载地址
  • 【春秋云境】CVE-2024-23897-Jenkins 2.441之前版本存在任意文件读取漏洞
  • RuoYi若依框架学习:多环境配置
  • 电子秤PCBA方案应用解决方案设计
  • Java面试:ArrayList 和 LinkedList 的区别是什么?谈谈你对ArrayList和LinkedList的理解
  • 500. 键盘行 哈希表 力扣 Python 难度指数:3
  • JavaScript 定时器与动画基础
  • DAY80服务攻防-中间件安全HW2023-WPS 分析WeblogicJettyJenkinsCVE
  • 旧衣回收小程序搭建,开发功能优势
  • CORDIC算法笔记整理
  • 全局中断总开关位与各个中断源对应的寄存器使能位开启顺序
  • Vscode把全部‘def‘都收起来的快捷键
  • Django 对数据库的增删改查
  • [译] K8s和云原生
  • `torch.utils.data`模块
  • PostgreSQL 向量扩展插件pgvector安装和使用
  • 高等数学 第11讲 多元函数偏导数的计算与应用_复合函数求偏导_隐函数求偏导_条件极值
  • 什么是原生IP?
  • QT+ESP8266+STM32项目构建三部曲二--阿里云云端处理之云产品流转
  • 学习threejs,绘制二维线
  • 洛谷P1197.星球大战