深刻理解JDK中线程池的使用

news2024/9/23 16:19:07

一、线程池状态

线程结构关系

ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位标识线程数量.

注意 : 第一位为符号位,所以RUNNING状态为负数,最小.

这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次cas原子操作进行赋值.

// c 为旧值,ctlOf返回结果为新值
ctl.compareAndSet(c,ctlOf(targetState,workerCountOf(c)));

// rs 为高3位代表线程池状态, wc 为低29位代表线程个数,ctl 是合并它们
private static inbt ctlOf(int rs,int wc)
{
    return rs | wc;
}

二、线程池七大核心参数

查看线程ThreadPoolExecutor的构造方法

  • corePoolSize : 核心线程数目(最终能够保留的最多的线程数)
  • maximumPoolSize: 最大的线程数目
  • keepAliveTime: 线程的生存时间,也就是空闲线程的存活时间(针对救急线程而不是核心线程)
  • unit: 存活时间的单位.
  • workQueue: 阻塞队列,核心线程数处理不过来的任务,首先都是放入阻塞队列当中
  • threadFactory: 线程工厂,可以给线程创建一个名字,方便日志追踪
  • handler: 拒绝策略.

(一) 拒绝策略

  • AbortPolicy: 让调用者抛出RejectedExecutionException异常,这是默认策略
  • CallerRunsPolicy: 让调用者直接调用run方法.
  • DiscardPolicy: 放弃本次任务
  • DiscardOldestPolicy: 放弃队列中最早的任务,将本任务取而代之.

new ThreadPoolExecutor.AbortPolicy() //默认,抛异常

new ThreadPoolExecutor.DiscardPolicy()//直接放弃

new ThreadPoolExecutor.CallerRunsPolicy()//调用run

new ThreadPoolExecutor.DiscardOldestPolicy()//放弃最早的任务,也就是等待最久的任务

三、默认线程池

jdk帮我们默认实现了几种线程池

(一) newFixedThreadPool

// 创建一个固定大小的线程池,核心线程数与最大线程数均为2,队列容量为Integer.MAX_VALUE
ExecutorService threadPool = Executors.newFixedThreadPool(2);

(二) newCachedThreadPool

// 缓存线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,来一个任务就创建一个线程执行,
//队列为SynchronousQueue
//synchronized队列,只有线程来获取到任务,队列才可以往里面插入任务
//适合执行很多短期异步任务,每个线程存活时间为60s
ExecutorService threadPool1 = Executors.newCachedThreadPool();

(三) newSingleThreadExecutor

// 单线程线程池,核心线程数与最大线程数均为1,队列容量为Integer.MAX_VALUE
//适合执行少量耗时的同步任务,每个任务都是串行执行
//一个任务抛出异常,其他的任务将会创建一个新的线程执行
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();

四、自定义线程工厂

有时候我们可能需要排查日志,但是如果使用Jdk自带的线程工厂,会导致日志不清晰,不清楚这个线程池具体运用于哪种业务,所以我们需要自定义一个线程工厂来帮我们打印更有意义的日志

我们来查看一下JDK源码,看看JDK是如何打印线程日志的

原来是通过AtomicInteger原子性CAS操作,是线程数加1.所以我们查看的日志就是namePrefix拼凑而成.我们可以效仿这样的方式,实现一个自己的线程工厂.

如:

public class LogThreadPoolFactory implements ThreadFactory {

    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    //传入线程池的名字参数
    LogThreadPoolFactory(String prefix) {
        @SuppressWarnings("removal")
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        //如果传入是空值,则恢复原来的线程池名
        if(prefix == null || prefix.isEmpty()){
            prefix = "pool";
        }
        namePrefix = prefix + "-" +
                poolNumber.getAndIncrement() +
                "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        if (t.isDaemon()) {
            t.setDaemon(false);
        }
        if (t.getPriority() != Thread.NORM_PRIORITY) {
            t.setPriority(Thread.NORM_PRIORITY);
        }
        return t;
    }
}

五、饥饿问题

(一) 问题描述

饥饿问题是指,由于线程池分工不明确,也就是线程池可以做很多事情,由于线程池资源有限导致,其他任务不能正常执行,而导致饥饿问题出现.举例说明:

也就是像在饭馆,如果一个人可以点菜又可以做饭,另一个人同样也可以.

两个人都是点完菜之后,然后自己进入厨房开始做饭.

突然来了两个顾客,然后两个人都去点菜了,导致没有人去做菜,导致饥饿问题产生

体现到代码层面为:

// 饥饿问题: 线程池中线程数不足,导致任务积压,导致任务无法及时执行,导致程序卡死。
@Slf4j
public class HungryQuestionDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        threadPool.execute(()->{
            log.info("开始点餐");
            Future<String> future = threadPool.submit(() -> {
                String cooking = cooking();
                return cooking;
            });
            try {
                log.info("菜品为:{}", future.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        threadPool.execute(()->{
            log.info("开始点餐");
            Future<String> future = threadPool.submit(() -> {
                String cooking = cooking();
                return cooking;
            });
            try {
                log.info("菜品为:{}", future.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
    private static final Random RANDOM = ThreadLocalRandom.current();
    private static final String[] DISHES = {"肉夹馍", "火锅", "烤鸭", "麻辣烫"};
    public static String cooking(){
        return DISHES[RANDOM.nextInt(DISHES.length + 1)];
    }
}

执行结果

(二) 问题解决

我们明确线程池的分工,一个用来点菜,一个用来做饭,这样我们就能完美的解决问题.可以用上之前的自定义线程工厂

代码如下

//分类使用线程池
@Slf4j
public class HungryDealMethod {
    public static void main(String[] args) {
        ExecutorService order = Executors.newFixedThreadPool(1, new LogThreadPoolFactory("点餐员"));

        ExecutorService cooker = Executors.newFixedThreadPool(1, new LogThreadPoolFactory("厨师"));

        order.execute(()->{
            log.info("开始点餐");
            cooker.submit(() -> {
                String cooking = cooking();
                log.info("菜品为:{}", cooking);
                return cooking;
            });
        });
        order.execute(()->{
            log.info("开始点餐");
            cooker.submit(() -> {
                String cooking = cooking();
                log.info("菜品为:{}", cooking);
                return cooking;
            });
        });
    }
    private static final Random RANDOM = ThreadLocalRandom.current();
    private static final String[] DISHES = {"肉夹馍", "火锅", "烤鸭", "麻辣烫"};
    public static String cooking(){
        return DISHES[RANDOM.nextInt(DISHES.length + 1)];
    }
}

执行结果

六、定时线程池

(一) Timer

在jdk5时,引入了Timer的工具类,用来处理定时任务的处理.但是他有很多的局限性.所以在后面基本不会被使用了.但是我们可以了解定时线程池的由来,解决Timer什么问题.

Timer实际上是采用了单线程模式,这样会导致所有定时任务都是串行执行.

//timer 定时器: 定时执行任务,但是是单线程,串行执行
@Slf4j
public class TimerDemo {
    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                log.info("task1");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.info("task2");
            }
        };
        log.info("start...");
        timer.schedule(task1,1000);
        timer.schedule(task2,1000);
        Thread.sleep(3020);
        timer.cancel();
    }
}

执行结果

注意到,task1的执行任务时长影响到了task2的开始执行时间.

(二) ScheduledThreadPool

我们可以自定义线程池的大小,使一些定时任务并行执行(注意看注释,能够很好的帮助理解)

@Slf4j
public class ScheduleAPIDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
//        method1(scheduledThreadPool);
        log.info("start...");
        //2.按照固定速率执行任务,实际情况取间隔时间与执行时间的最大值,所以2秒后才执行第二次
        //如果执行时间大于间隔时间,则以执行时间为准
//        scheduledThreadPool.scheduleAtFixedRate(()->{
//            log.info("schedule task");
//            try {
//                Thread.sleep(2000);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//        },1,1, TimeUnit.SECONDS);
        //3.按照固定时间间隔执行任务,等任务完成以后,才开始间隔时间的流逝,所以2+1=3秒后才第二次执行

        //注意:线程池执行任务中如若有异常发生,是不会影响其他任务的执行的,而且不会抛出异常,所以建议在任务中捕获异常,并进行处理
        //或者使用future.get()方法获取任务执行结果,如果有异常则获取的就是异常信息
        scheduledThreadPool.scheduleWithFixedDelay(()->{
            log.info("schedule task");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },1,1, TimeUnit.SECONDS);

    }

    private static void method1(ScheduledExecutorService scheduledThreadPool) {
        //1.多线程可以并行执行(解决Timer单线程串行执行问题)
        scheduledThreadPool.schedule(()->{
            log.info("schedule task1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },1, TimeUnit.SECONDS);

        scheduledThreadPool.schedule(()->{
            log.info("schedule task2");
        },1, TimeUnit.SECONDS);
    }
}

(三) 定时线程池的运用

假设我们现在需要每周四晚6点执行一个任务我们如何处理?

//让在一个时间点执行定时任务
//每周四下午6点执行一次任务
@Slf4j
public class ScheduleTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
        //获取到当前时间
        LocalDateTime now = LocalDateTime.now();
        //求得下一个定时任务执行的初始延迟时间
        LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        System.out.println("start time: " + time);
        //如果求出来的time小于now,则time需要增加一周.
        if(now.isAfter(time)){
            time = time.plusWeeks(1);
        }
        long innitDelayTime = Duration.between(now, time).toMillis();
        System.out.println(innitDelayTime);
        System.out.println("proccess time: " + time);
        long period = 1000; // 1s
        //innitDelayTime,初始等待时间
        //period,间隔多长时间执行一次,也就是频率
        //第三个参数代表前面两个参数的时间单位
        scheduledThreadPool.scheduleAtFixedRate(()->{
            log.info("runnning...");
        },innitDelayTime,period, TimeUnit.MILLISECONDS);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2068243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何从零编程实现《黑神话:悟空》

随着游戏行业的不断发展&#xff0c;越来越多的技术被应用于游戏的开发之中。其中&#xff0c;《黑神话&#xff1a;悟空》作为一款备受期待的动作冒险游戏&#xff0c;不仅以其精美的画面和丰富的剧情吸引了无数玩家的关注&#xff0c;还因其背后的技术实现了独特的游戏体验。…

怎么生成一个springboot的项目

这个很简单,只是想记录一下使用springboot的创建工具去创建项目 创建完成之后,删除一些不必要的东西 确认springboot的版本号,我这里要用2.4.0 刷新一下maven,等待下载完成就完成了

T6:好莱坞明星识别

文章目录 **T6周&#xff1a;好莱坞明星识别****一、前期工作**1.设置GPU&#xff08;用CPU可忽略该步骤&#xff09;2.导入数据3.查看数据 **二、数据预处理**1.加载数据2.可视化数据3.配置数据集 **三、构建CNN网络模型****四、编译模型****五、训练模型****六、模型评估****…

Circuitjs 分支电路(子电路, subcircuit)功能简介

在 circuitjs 中, 可以使用 分支电路 来实现自定义的"黑盒"器件. 分支电路 也称为 子电路(subcircuit). 因为菜单上已经叫成了 分支电路, 以下均称为 分支电路. 通过分支电路, 可以实现对电路的封装与抽象, 从而达到模块化并简化电路的目的. 更进一步的, 被抽象的黑盒…

Apache Paimon V0.9最新进展

摘要&#xff1a;本文整理自 Paimon PMC Chair 李劲松老师在 8 月 3 日 Streaming Lakehouse Meetup Online&#xff08;Paimon x StarRocks&#xff0c;共话实时湖仓架构&#xff09;上的分享。主要分享 Apache Paimon V0.9 的最新进展以及遇到的一些挑战。 一、Paimon&#x…

无人系统特刊合集(二)丨Springer特刊推荐

期刊推荐 期刊征稿&#xff1a;JOURNAL OF INTELLIGENT & ROBOTIC SYSTEMS Journal of Intelligent & Robotic Systems是一本同行评议的期刊&#xff0c;致力于智能系统和机器人技术的理论和实践。 专注于无人系统、机器人和自动化以及人机交互等领域。 在每期中都包…

天猫 登录滑块 淘系滑块分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关 前言 玩了几天现在才有空研究轨迹直接用了之前的…

日志组件导致的内存溢出问题分析

1、 内存溢出日志 普通的http请求&#xff0c;导致堆内存直接溢出&#xff0c;看了下代码实现非常简单的一次DB查询且数据量也比较小&#xff0c;不可能导致内存溢出呢 java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at…

网上花店设计+vue

TOC ssm017网上花店设计vue 绪论 1.1 选题背景 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。计算机软件可以针对不同行业的营业特点以及管理需求&a…

CSS3页面布局-三栏-中栏流动布局

三栏-中栏流动布局 用负外边距实现 实现三栏布局且中栏内容区不固定的核心问题就是处理右栏的定位&#xff0c; 并在中栏内容区大小改变时控制右栏与布局的关系。 控制两个外包装容器的外边距&#xff0c;一个包围三栏&#xff0c;一个包围左栏和中栏。 <!DOCTYPE html&…

计算机毕业设计 在线问诊系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

海运整箱成本与拼箱成本对比 | 国际贸易服务平台 | 箱讯科技

整箱和拼箱 在集装箱运输业务中&#xff0c;我们把一个集装箱、一个出口人、一个收货人、一个目的港&#xff0c;满足这“四个一”条件的货物叫做整箱货&#xff0c;而把一个集装箱、出口人、收货人和目的港这三项之中只要有一项是在两个或两个以上的出口运输货物&#xff0c;就…

批量将labelme的json文件转为png图片查看

文章目录 前提修改 l a b e l m e labelme labelme然后你就可以在这个环境下用代码批量修改了 前提 安装anaconda或者miniconda安装labelme 修改 l a b e l m e labelme labelme 查看labelme所处环境的路径&#xff1a;conda info --envs 比如我的是在py39_torch里面 修改la…

Anki自动生成语音

文章目录 前言安装插件制作音频一些注意事项语音消失现象不同端出现媒体文件丢失 参考文章 前言 已经实现了通过使用Obsidian实现Anki快速制卡。 对于语言学习&#xff0c;仅仅只有不同语言文字的对照是不够的&#xff0c;我们还需要声音。 所以就需要加入音频。 幸好 Anki…

laravel “Class \“Redis\“ not found“ 如何解决?

laravel “Class “Redis” not found” 如何解决 问题&#xff1a;laravel 安装好后&#xff0c;运行报错提示&#xff1a;“Class “Redis” not found” 分析&#xff1a;程序并没有用到redis&#xff0c;百度了一下&#xff0c;初步锁定可能是php环境的原因&#xff0c;运…

【数字ic自整资料】存储器及不同端口RAM对比

参考资料 【FPGA】zynq 单端口RAM 双端口RAM 读写冲突 写写冲突_双口ram-CSDN博客 华为海思数字芯片设计笔试第五套_10、下列不属于动志数组内建函数的是: a lengtho b. new c. delete() d-CSDN博客 目录 1、计算器典型存储体系结构 2、三种不同端口RAM &#xff08;1&…

钢包智慧管理平台

钢包智慧管理平台基于海康、大华视频监控&#xff0c;实现对钢包的全动态管理&#xff0c;实时检测钢包的温度数据变化&#xff0c;也可以随时查询时间区间内的钢包温度数据变化。 平台基于springboot vue前后台分离技术开发&#xff0c;视频基于zlmedia的转码拉流。实现了视频…

嵌入式学习day33

tcp的特点 面向字节流特点&#xff0c;会造成可能数据与数据发送到一块&#xff0c;成为粘包&#xff0c;数据之间不区分 拆包 因为缓冲区的大小&#xff0c;一次性发送的数据会进行拆分&#xff08;大小不符合的时候&#xff09; 就和水一样一次拆一次沾到一块&#xff0c…

快速入门Spring

目录 为什么要学Spring&#xff1a; Spring框架诞生的背景&#xff1a; Spring是什么&#xff1a; 接下来我们就要解决Spring怎么用这个问题 BeanFactory快速入门 IOC思想的体现 BeanFactory快速入门 DI思想的体现 开始学Spring时我们要了解以下几个问题 为什么要学习Sp…

深入理解滑动窗口算法及其经典应用

文章目录 什么是滑动窗口&#xff1f;经典题型分析与讲解**1. 长度最小的子数组****2. 无重复字符的最长子串****3. 最长重复子数组****4. 将x减到0的最小操作数**5. 水果成篮 (LeetCode 904)6. 滑动窗口最大值 (LeetCode 239)7. 字符串中的所有字母异位词 (LeetCode 剑指 Offe…