用“女神的一群舔狗”的例子深入理解线程池

news2024/11/20 7:08:10

 

假如有一个妹子(肤白貌美身材好)

同一时间只能谈一个对象,但是新鲜感过去之后就没什么意思了,就想换个对象,但是更换对象的操作效率比较低,需要做到:

1. 想办法和现有对象分手

2.吸引到下一个舔狗

这两个操作都是需要时间的,如何优化时间呢?

在和现任对象交往的过程中和其他舔狗(男闺蜜)搞暧昧,此时和现任分手之后就可以无缝衔接,大大提升效率,这些舔狗如果不止一个,就称为“舔狗池”,当玩腻了之后,再给前任一点甜头,就又可以再续前缘,也就是线程的复用,线程池也是一样的道理

1. 线程池

在之前我们写的代码中,用到线程就创建,用完之后线程就消失了,这样会浪费操作系统的资源,也存在一些弊端,通过线程池就可以解决这个问题

线程池是一种线程使用模式,它维护着多个线程,等待着监督管理者分配可并发执行的任务

线程池的核心原理:

  1. 创建一个空的线程池
  2. 提交任务时,线程会创建新的线程对象,任务分配完毕,线程归还给线程池,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 如果提交任务时,线程池中没有空闲线程,也无法创建新的线程,任务就会排队等待

执行的很多代码逻辑都是要用户态的代码和内核态的代码配合完成,而应用程序又有很多,这些应用程序都是由内核统一负责管理和服务,所以内核中的工作可能是非常繁忙的,也就导致了提交给内核的工作可能是不可控的,因此,通常认为,纯用户态操作,就比经过内核的操作效率更高。

Java标准库里也提供了线程池的实现类 ThreadPoolExecutor

下面是四种构造方法,

接下来介绍一下第四个构造方法中的参数都表示什么:

int corePoolSize : 核心线程数 (核心线程会始终存在于线程池内部,非核心线程,繁忙的时候被创建出来,空闲时就释放掉)

int maximumPoolSize 最大线程数【(核心线程数+非核心线程数)的最大值】

long keepAliveTime: 非核心线程允许空闲的最大时间(超过这个时间就会被释放)

TimeUnit unit: 非核心线程允许空闲的最大时间的单位

BlockingQueue<Runnable> workQueue 工作队列(线程池的工作过程就是一个典型的“生产者消费者模型”,这里的队列就可以指定容量和类型)

ThreadFactory threadFactory: 线程工厂(Thread类的工厂类,通过这个类,完成Thread的实例创建和初始化操作,此处的 threadFactory就可以针对线程池里的线程进行批量的设置属性),一般情况下使用 Executors.defaultThreadFactory()即可

RejectedExecutionHandler handler 拒绝策略

解释:核心线程就是指一直存在于线程池中的线程,两个空闲时间就是值,创建出来的临时线程空闲的时间,超过这个时间就意味着这靠核心线程就足以完成当前提交的任务,就需要销毁临时线程,节约资源,要执行的任务过多时的解决方案指的是,当前线程池中线程的数量已经达到了最大,并且阻塞队列也已经排满了,就需要把多出来的任务踢出去

下面是四种拒绝策略的描述:

再把女神搬出来解释这四种拒绝策略就是: 

你想和女神约会,女神周一到周日的约会行程排满了,女神就会拒绝你,下面就是四种拒绝策略

1. 女神直接翻脸:老娘看不上你

2.女神表示拒绝,让你原来想干嘛干嘛去

3.把等待和女神约会最长时间那个舔狗拒绝了,让你上位

4.把等待时间最短的那个舔狗拒绝掉,让你上位

不断地提交任务,会有以下三个临界点:

1. 当核心线程满了之后,再提交任务就会排队

2. 当核心线程和阻塞队列都满了之后,就会创建临时线程

3. 当核心线程,阻塞队列,临时线程都满了之后,会触发任务的拒绝策略

1.1. 线程池的使用

Executors是一个工具类,对ThreadPoolExecutor进行了封装,提供了一些常用的方法:

先来演示一下:

public static void main(String[] args) {
    ExecutorService service = Executors.newFixedThreadPool(4);
    for (int i = 0; i < 100; i++) {
        service.submit(()->{
            Thread current = Thread.currentThread();
            System.out.println(current.getName() + ": " + i);
        });
    }
}

此时发现 i 出现了错误,其实还是之前一直提到的变量捕获的问题,这里换种解决方法:

每次都创建一个局部变量来接收,就可以了

但是此时还有一个问题,运行程序之后发现程序并没有终止

是因为线程池创建出来的线程默认是“前台线程”,虽然main线程结束了,但是这些线程池的前台线程仍然是存在的,可以使用shutdown方法来把所有的线程终止掉,不过也不能立即终止,要确保所有的任务都执行完毕之后再终止,所以可以再添加一个sleep方法

public class ThreadDemo22 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 100; i++) {
            int id = i;
            service.submit(()->{
                Thread current = Thread.currentThread();
                System.out.println(current.getName() + ": " + id);
            });
        }
        //确保所有任务都执行完毕
        Thread.sleep(1000);
        //终止所有线程
        service.shutdown();
    }
}

1.2. 自定义线程池

根据线程池的特性,还可以模拟实现一个固定线程数目的线程池

先来简单的实现一下:

class MyThreadPoll{
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
    public MyThreadPoll(int n){
        //创建n个线程
        for(int i = 0;i < n;i++){
            Thread thread = new Thread(()->{
                while (true){
                    //循环从队列中取出任务
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            thread.start();
        }
    }
    //提交任务
    public void submit(Runnable runnable){
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

上面的自定义线程池中,在构造方法里通过for循环来不断地创建线程,通过向阻塞队列中获取线程,并调用run方法执行任务,在提交任务时就是把任务加入到队列中

当然,上面的线程池是比较简陋的, 一些扩容,拒绝策略等功能还没有实现。

下面定时器的例子虽然和题目无关,但是还是夹带了

2. 定时器

有了定时器就可以给女神准时发消息了,嘿嘿嘿~~

2.1. 定时器的使用

定时器就相当于是一个闹钟,时间到了之后开始执行某个任务,Java中的定时器是TimerTask类和Timer类,TimerTash是一个抽象类,用于表示一个可以被定时器执行的任务,Timer类用于安排TimerTask在指定的时间执行,下面来演示一下:

public class ThreadDemo24 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },2000);
        System.out.println("程序开始执行...");
    }
}

也可以多定几个任务:

public class ThreadDemo24 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);
        System.out.println("程序开始执行...");
    }
}

2.2. 定时器的实现

模拟一个定时器的实现需要以下步骤:

  1. 创建类,描述一个要执行的任务是什么(任务的内容,任务的时间)
  2. 管理多个任务(通过一个合适的数据结构把任务存储起来)
  3. 有专门的线程执行任务

先来描述一下任务:

class MyTimerTask implements Comparable<MyTimerTask> {
    //任务
    private Runnable runnable;
    //时间,通过毫秒时间戳来表示这个任务具体什么时候执行
    private long time;

    public MyTimerTask(Runnable runnable, long dely) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + dely;
    }

    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

接下来就是管理任务

关于用什么数据结构来存储这些任务,可以来分析一下,如果说使用顺序表或者链表这样的结构可行吗,这样的话每次查找执行那个任务都需要遍历一遍,效率低下,所以说可以使用堆来存储,可以很好的找到最小值,只需要判断最小的那个任务是否到时间了即可

由于创建的堆是自定义类型,所以说MyTimeTash类还需要实现Comparable接口,重写compareTo方法

再来看MyTimer类:

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object lock = new Object();

    public MyTimer() {
        //创建线程,来负责执行任务
        Thread t = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (queue.isEmpty()) {
                        continue;
                    }
                    MyTimerTask current = queue.peek();
                    //需要执行任务的情况
                    if (System.currentTimeMillis() >= current.getTime()) {
                        current.run();
                        //执行过的任务需要从队列中删除
                        queue.poll();
                    } else {
                        continue;
                    }
                }
            }
        });
        //开启线程执行任务
        t.start();
    }

    public void schedule(Runnable runnable, long dely) {
        synchronized (lock) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, dely);
            queue.offer(myTimerTask);
        }
    }
}

创建线程之后,不断地从队列中取出任务来执行,执行完的任务要从队列中移除

同时,为了解决线程安全问题,还需要把出队和入队的操作都加上锁

但是,上面的代码还是存在几个潜在的问题的:

  1. 初始情况下,队列为空,由于continue的存在就会在循环中会反复地加锁释放锁,显然是不行的,所以可以采用等待唤醒机制来修改:

这里吧continue改为wait会更好一点

唤醒的时机就是每次添加任务的时候:

  1. 如果说当前时间是10:30,定的时间是12:00,那么期间一个半小时走的都是else分支,还是会不断地加锁

所以说这里也需要一个等待,不过可以不用像刚开始那样一直等,因为这时候肯定是知道要等多少时间的,直接采用有参版本就行

q : 如果使用 sleep 可以吗

a : 不可以

  1. wait可以被唤醒,如果提前加入了一个任务,就需要唤醒线程来执行任务,但是sleep不能唤醒
  2. sleep是抱着锁睡的,其他线程拿不到锁(1中任务也不能添加)

最后还有一个问题:关于为什么不使用PriorityBlockingQueue

由于PriorityBlockingQueue自身实现的方法本来就已经带锁了,这样就出现了锁的嵌套,就容易出现死锁的问题

下面是优化版的完整代码:

class MyTimerTask implements Comparable<MyTimerTask> {
    //任务
    private Runnable runnable;
    //时间,通过毫秒时间戳来表示这个任务具体什么时候执行
    private long time;

    public MyTimerTask(Runnable runnable, long dely) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + dely;
    }

    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object lock = new Object();

    public MyTimer() {
        //创建线程,来负责执行任务
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (lock) {
                        if (queue.isEmpty()) {
                            wait();
                        }
                        MyTimerTask current = queue.peek();
                        //需要执行任务的情况
                        if (System.currentTimeMillis() >= current.getTime()) {
                            current.run();
                            //执行过的任务需要从队列中删除
                            queue.poll();
                        } else {
                            lock.wait(current.getTime() - System.currentTimeMillis());
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //开启线程执行任务
        t.start();
    }

    public void schedule(Runnable runnable, long dely) {
        synchronized (lock) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, dely);
            queue.offer(myTimerTask);
            lock.notify();
        }
    }
}

public class ThreadDemo25 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() -> {
            System.out.println("hello1");
        }, 1000);
        myTimer.schedule(() -> {
            System.out.println("hello2");
        }, 2000);
        myTimer.schedule(() -> {
            System.out.println("hello3");
        }, 3000);
        System.out.println("开始执行");
    }
}

 最后,xdm切记,不要做舔狗~~

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

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

相关文章

高低压配电系统中电弧光的危害有多大?

摘要 故障电弧是一种常见的电气故障现象&#xff0c;尤其在配电系统中&#xff0c;可能对设备安全和电力供应造成严重影响。本文旨在探讨故障电弧对配电系统的危害&#xff0c;并提出相应的预防措施&#xff0c;以增强系统的可靠性和安全性。通过对故障电弧的形成机制、危害分…

软件设计师试题

1、以下关于RISC&#xff08;精简指令集计算机&#xff09;特点的叙述中&#xff0c;错误的是&#xff08; B &#xff09;。 A.对存储器操作进行限制&#xff0c;使控制简单化 B.指令种类多&#xff0c;指令功能强 C.设置大量通用寄存器 D.选取使用频率较高的一些指令&…

利用Python快速提取字体子集

来自&#xff1a;Python大数据分析 费弗里 在我们日常进行数据可视化、web应用开发等场景中&#xff0c;经常会用到一些特殊的非系统自带字体&#xff0c;尤其是中文字体&#xff0c;由于包含的字符数量众多&#xff0c;因此体积一般都比较大&#xff0c;这在进行数据可视化读取…

C++当中的继承

在C当中继承是一个非常重要的语法。我们可以使用继承快速的进行代码的复用以及对代码进行扩展操作。首先我们来进行学习继承的基本语法。 &#xff08;一&#xff09;继承的语法方式 还记得我们之前学习的访问限定符吗&#xff1f;就是class里面的private&#xff0c;public&am…

直播间没有自然流,如何突破?

如果你的直播间完全没有自然流量&#xff0c;不用担心&#xff0c;有四种方法可以解决这个问题。 第一种方法是延长直播时长。如今的账号系统与以前不同&#xff0c;现在自然流量非常珍贵。以前&#xff0c;新账号即使没有数据&#xff0c;平台也会给一些流量&#xff0c;但现在…

校园管理新篇章:Spring Boot系统实现策略

第3章 系统分析 3.1 需求分析 校园管理系统主要是为了提高用户的工作效率和更方便快捷的满足用户&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各个模块是通过许多今天的发达系统做出合理的分析来确定考虑用户的可操作性&#xff0c;遵循开发的系…

jmeter设置全局token

1、创建setup线程&#xff0c;获取token的接口在所有线程中优先执行&#xff0c;确保后续线程可以拿到token 2、添加配置原件-Http信息头管理器&#xff0c;添加取样器-http请求 配置好接口路径&#xff0c;端口&#xff0c;前端传参数据&#xff0c;调试一下&#xff0c;保证获…

2024社群空间站全自动付费进群系统九块九进群源码

多种玩法&#xff1a;付费VIP玩法、同城行业群裂变玩法、全民K歌群裂变玩法、拼多多群玩法、VIP领取百度网盘资料玩法、单群付费玩法;

WTL580-电子锁微波雷达应用解决方案,5.8GHz精准人体感知,触发高效交互新体验

一、简介 随着智能电子门锁普及&#xff0c;电子门锁的市场也随着打开&#xff0c;安装智能化电子门锁也为大势所趋。现我司推出基于WTL580微波雷达的电子锁应用方案&#xff0c;通过检测门锁周围是有活动人体存在来激活门锁。我司WTL580微波雷达方案采用5.8GHz微米波雷达传感器…

mac系统安装最新(截止2024.9.13)Oracle JDK操作记录

文章目录 下载JDK22配置环境变量验证环境变量是否生效整体命令如下 下载JDK22 打开最新版Oracle JDK下载地址 选择想要安装的JDK版本&#xff0c;然后选择适合兼容Mac机器的版本&#xff08;Intel/arm&#xff09;&#xff0c;建议直接下载安装程序&#xff0c;可视化安装 默…

栈的定义和基本操作的实现

写代码&#xff1a;定义顺序存储的栈&#xff08;数组实现&#xff09;&#xff0c;数据元素是 int 型 写代码&#xff1a;基于上述定义&#xff0c;实现“出栈、入栈、判空、判满”四个基本操作 写代码&#xff1a;定义链式存储的栈&#xff08;单链表实现&#xff09; 写代…

零钱兑换二维dp实现(力扣--动态规划)

文章目录 1.题目描述2.解题思路3.代码实现 1.题目描述 题目链接&#xff1a;零钱兑换 2.解题思路 1.确定二维dp[i][j]的含义&#xff1a; dp[i][j] 前i个物品任取&#xff0c;装入容量为j的背包种&#xff0c;最少的硬币个数是dp[i][j] 2.确定递推公式&#xff1a; dp[i][j]…

【日语学习必备】5款超准实时翻译软件,让你的网课不再有障碍!

日语水平不过关&#xff0c;没办法实时听懂日语会议或日语网课内容怎么办&#xff1f; 两种方法&#xff01; 一、利用日语实时翻译软件&#xff0c;也就是所谓同声传译的方式实时将日语转换为中文 二、先将会议或网课等内容录制下来&#xff0c;再借助语音或视频翻译软件&am…

利士策分享,探索无界:心灵之旅,发现未知精彩

利士策分享&#xff0c;探索无界&#xff1a;心灵之旅&#xff0c;发现未知精彩 梦想的种子&#xff0c;在心田生根发芽 正如每一颗种子都蕴含着生命的奥秘&#xff0c;每个人心中那颗探索的种子&#xff0c;也藏着对未知世界的渴望与追求。它告诉我们&#xff0c;成长不仅仅…

Unite Shanghai 2024 技术专场 | Unity 6及未来规划:Unity引擎和服务路线图

在 2024 年 7 月 24 日的 Unite Shanghai 2024 技术专场演讲中&#xff0c;Unity 高级技术产品经理 Jeff Riesenmy 带来演讲 Unity 6 and Beyond: A Roadmap of Unity Engine and Services。作为本次 Unite 首场专题演讲&#xff0c;他介绍了 Unity 引擎的最新进展及其配套的工…

猫头虎分享:15种数码苹果16抢购攻略

猫头虎分享&#xff1a;15种数码苹果16抢购攻略 大家好&#xff0c;我是猫头虎&#xff01;今晚8点&#xff0c;就是大家期待已久的苹果16抢购时刻&#xff0c;你准备好了吗&#xff1f;为了帮助大家顺利抢到心仪的机型&#xff0c;我精心准备了15种抢购指南&#xff0c;总有一…

[C#学习笔记]LINQ

视频地址&#xff1a;LINQ入门示例及新手常犯的错误_哔哩哔哩_bilibili 强烈推荐学习C#和WPF的朋友关注此UP&#xff0c;知识点巨多&#xff0c;讲解透彻&#xff01; 一、基本概念 语言集成查询(Language-Intergrated Query) 常见用途 .Net原生集合(List&#xff0c;Arra…

“勇者斗恶龙”即将上演,乐道L60剑指Model Y

文/王俣祺 导语&#xff1a;只要说到“二十多万”“大品牌”“纯电SUV”这一系列关键词&#xff0c;特斯拉Model Y一定是绕不开的车型&#xff0c;从近几年的销量来看&#xff0c;目前市面上很难有车型能“硬刚”。不过既然有“恶龙”就会有“勇者”&#xff0c;蔚来子品牌乐道…

“薅羊毛”时间到, 容声以旧换新“钜惠”升级

9月13日&#xff0c;由佛山市商务局、顺德区人民政府指导&#xff0c;海信家电集团主办的以旧换新佛山发布活动启幕。 海信家电&#xff08;SZ 000921&#xff0c;HK 00921&#xff09;旗下容声冰箱叠加国家以旧换新补贴&#xff0c;把“以旧换新”升级到“品质换新”&#xf…

汤臣倍健业绩大幅下滑:经销商减少131家,销售费用转化不达预期

《港湾商业观察》廖紫雯 在膳食营养补充剂&#xff08;VDS&#xff09;行业竞争加剧的情况下&#xff0c;日前&#xff0c;汤臣倍健股份有限公司&#xff08;以下简称&#xff1a;汤臣倍健&#xff0c;300146.SZ&#xff09;披露2024年半年报业绩情况。上半年&#xff0c;公司…