Java多线程(三)多线程的模式--(阻塞队列,定时器,线程池)

news2025/2/26 0:34:00

多线程的模式--(阻塞队列,定时器,线程池)

多线程模式:

阻塞队列(线程安全)

重点是如何自己去实现这种数据结构:

​编辑

定时器:

实现一个定时器:

线程池:

实现线程池


 

多线程模式:

软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照

这个套路来实现代码, 也不会吃亏。大佬们为我们操碎了心。

单例模型(某一个类,在进程中只有唯一一个实例)

分为:饿汉模式 懒汉模式

饿汉模式:就是将文件所有的内容都读到内存中,并显示。(小规模就好,太多了内存不够,所以懒汉模式)

class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
   }
}

被static修饰,该属性是类的属性(类对象上),JVM中,每个类的对象只有唯一一份,类对象的成员自然也是唯一一份。

private将new操作给静止掉,在类内部把实例创建好同事静止外部重新创建实例,此时,就可以保证单例的特性。

懒汉模式:只读取文件的一小部分。把当前屏幕填充上,如果用户翻页了,再读其他文件内容,如果不翻页,就可以节约运算资源。

单线程:核心思想,非必要,不创建。第一次使用的时候才创建实例

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

多线程:

上面的代码是不安全的,为什么?因为在多线程下,同时调用 getInstance 方法, 就可能导致

创建出多个实例。

加上 synchronized 可以改善这里的线程安全问题

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

加锁进行实例化对象,是很耗资源。其实一个实例创建后,在内存中已经存在,其他线程其实更多的是读操作。那么就没必要去进行加锁操作。

对上述代码改进:

class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        //判断是否为空,诺不为空,就不需进行加锁实例化。
        if (instance == null) {
            synchronized (Singleton.class) {
                //进行实例化判断
           if (instance == null) {
               instance = new Singleton();
               }
           }
       }
        return instance;
   }
}

两个if所代表的意义有所不同。

1.加锁if:把if和new变为原子操作

2.双重 if:减少不必要的加锁操作

3.使用volatile 禁止指令重排序,保证后续线程肯定拿到的是完整对象。

单例模式:线程安全问题:

饿汉模式:天然就是安全的,只是读操作

懒汉模式:不安全的,有读也有写。

阻塞队列(线程安全)

本质是一个循环队列,但是它带有阻塞特性

1.如果入队列为空,尝试出队列,就会阻塞等待。等待到队列不空为止。

2.如果队列满了,尝试入队,就会阻塞等待,等待到队列不满为止。

这个就有一个经典的模型进行解释--生产者消费者模型,什么是生产者消费者模型?其实简单理解为,生产效率与消费效率的比值。比如一个面包厂1小时生产4个面包,而此时有很多人等着吃面包。这个就是简单的生产者消费者模型。

这个数据结构的模式有几个好处:

1.可以让上下游模块之间,可以更好的“解耦和”

队列与具体业务无关,队列中的某一个线程挂了,不影响其他线程,比如电脑有时候网页会卡,但是某些功能还在运行。

2.削峰填谷

不知道各位有没有打游戏,王者农药肯定都听过,其中有个事情,在游戏早期,它出了一款皮肤,这个皮肤很受玩家喜欢,再上线的那一刻,众多玩家,蹲点购买。使得当时的支付系统蹦了几分钟。为了应对这种情况,阻塞队列就可以减少这种风险。

在Java中提供了一个阻塞队列的数据的集合,BlockingQueue

  • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
  • put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
  • BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

重点是如何自己去实现这种数据结构:

主要分为三步:

1.先实现一个普通队列

2.加上线程安全

3.加上阻塞功能

class MyBlockingQueue{
    //普通队列
    private int [] items=new int[1000];
    //规定head--tail的范围为有效范围
  volatile   private int head=0;
  volatile private int tail=0;
  volatile   private int size=0;
    //入队列
  synchronized   public void put(int elem) throws InterruptedException {
        //队列元素满了
        while (size==items.length){
            this.wait();
        }
        items[tail]=elem;
        tail++;
        //判断是否到达末尾,队列中的元素没有满的情况下
        if (tail==items.length){
            tail=0;
        }
      //可读性下差,开发效率慢
      //tail=tail%items.length;
        this.notify();
      size++;
    }
    //出队列
   synchronized public Integer take() throws InterruptedException {
        while (size==0){
            this.wait();
        }
        int value=items[head];
        head++;
        if (head==items.length){
            head=0;
        }
        this.notify();

       size--;
       return value;
    }
}

public class test8 {
    public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue();

        Thread t1=new Thread(()->{

            while (true){
                try {
                   int value= queue.take();
                   System.out.println("消费:"+value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread t2=new Thread(()->{
            int value=0;
            while (true) {
                try {
                    System.out.println("生产:"+value);
                    queue.put(value);
                    Thread.sleep(1000);
                    value++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });



    }
}

入队成功后其他线程才能出队,出队成功后其他线程才能入队。

定时器:

定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码。

在java中提供了Timer类。Timer 类的核心方法为 schedule ,其中包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)。

Timer timer = new Timer();

timer.schedule(new TimerTask() {

   @Override

   public void run() {

       System.out.println("hello");

  }

}, 3000);

实现一个定时器:

定时器的构成:

  1. 一个带优先级的阻塞队列(阻塞队列中的任务都有各自的执行时刻 delay. 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.)
  2. 队列中的每个元素是一个 Task 对象.
  3. Task 中带有一个时间属性, 队首元素就是即将运行的。
  4. 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为了方便后续,使用绝对的时间戳
    public long time;

    public MyTask(Runnable runnable,long delay){
        this.runnable=runnable;
        //获取当前时刻的时间戳+delay,作为任务的实际执行时间
        this.time=System.currentTimeMillis()+delay;
    }
    @Override
    public int compareTo(MyTask o) {
        //设置比较器,构建优先级队列
        return (int)(this.time-o.time);
    }
}

class MyTimer{
    //这个结构,带有优先级的阻塞对列,核心数据结构
    //创建一个锁对象
    private Object loker=new Object();
    private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();


    //此处的dalay 是一个形如3000这样的数字(多长时间后执行)
    public void schedule(Runnable runnable,long dalay){
        //根据参数,构造MyTask,插入队列即可
        MyTask myTask=new MyTask(runnable,dalay);
        queue.put(myTask);
        synchronized (loker){
            loker.notify();
        }
    }
    //构造线程
    public MyTimer(){
        Thread t=new Thread(()->{
            while (true) {
                try {
                    synchronized (loker){
                        MyTask myTask=queue.take();
                        long curTime=System.currentTimeMillis();
                        if (myTask.time <= curTime){
                            //时间到了,执行任务
                            myTask.runnable.run();
                        }else {
                            //时间还没到
                            //将刚刚取出的任务,重新塞回队列
                            queue.put(myTask);
                           loker.wait(myTask.time-curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
}

public class test10 {
}

线程池:

(就是就是装有很多线程的仓库,使用线程从里面拿就好)

线程池最大的好处就是减少每次启动、销毁线程的损耗。

 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.

返回值类型为 ExecutorService 通过 ExecutorService.submit 可以注册一个任务到线程池中.

submit放入线程

ExecutorService pool = Executors.newFixedThreadPool(10);

pool.submit(new Runnable() {

   @Override

   public void run() {

       System.out.println("hello");

  }

});

源代码:

 

  • corePoolSize:核心线程数(不会消失)
  • maximumPoolSize:最大线程数(核心线程数+零时线程)
  • keepAliveTime: 临时线程允许的空闲时间.
  • unit: keepaliveTime 的时间单位, 是秒, 分钟, 还是其他值.
  • workQueue: 传递任务的阻塞队列
  • threadFactory: 创建线程的工厂, 参与具体的创建线程工作.
  • RejectedExecutionHandler: 拒绝策略, 如果任务量超出线程池的负荷了接下来怎么处理:
    1. AbortPolicy(): 超过负荷, 直接抛出异常.
    2. CallerRunsPolicy(): 调用者负责处理
    3. DiscardOldestPolicy(): 丢弃队列中最老的任务.
    4. DiscardPolicy(): 丢弃新来的任务

Executors 创建线程池的4种方式

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的Timer.

实现线程池

  1. 核心操作为 submit, 将任务加入线程池中
  2. 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
  3. 使用一个 阻塞队列中组织所有的任务
  4. 每个 worker 线程要做的事情: 不停的从 阻塞队列中取任务并执行.
  5. 指定一下线程池中的最大线程数 maxWorkerCount,当前线程数超过这个最大值时, 就不再新增线程了。
class Worker extends Thread {
    private LinkedBlockingQueue<Runnable> queue = null;
    public Worker(LinkedBlockingQueue<Runnable> queue) {
        super("worker");
        this.queue = queue;
   }
    @Override
    public void run() {
        // try 必须放在 while 外头, 或者 while 里头应该影响不大
        try {
            while (!Thread.interrupted()) {
                Runnable runnable = queue.take();
                runnable.run();
           }
       } catch (InterruptedException e) {
       }
   }
}
public class MyThreadPool {
    private int maxWorkerCount = 10;
    private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();
    public void submit(Runnable command) {
        if (queue.size() < maxWorkerCount) {
            // 当前 worker 数不足, 就继续创建 worker
            Worker worker = new Worker(queue);
            worker.start();
       }
        // 将任务添加到任务队列中
        queue.put(command);
   }
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool();
        myThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("吃饭");
           }
       });

        Thread.sleep(1000);
   }
}

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

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

相关文章

两种解法解决LCR 008. 长度最小的子数组【C++】

文章目录 [LCR 008. 长度最小的子数组](https://leetcode.cn/problems/2VG8Kg/description/)解法暴力解法滑动窗口&#xff08;双指针法&#xff09; LCR 008. 长度最小的子数组 解法 暴力解法 //暴力解法&#xff1a; //使用双for循环依次遍历数组&#xff0c;罗列出所有情况…

HTML的段落中怎么样显示出标签要使用的尖括号<>?

很简单&#xff1a; 符号 < 用 < 替代&#xff1b; 符号 > 用 > 替代。 示例代码如下&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>HTML中怎样打出尖括号</title> </head> <b…

AI图片生成 discord 使用midjourney

参考: 不用找咒语了&#xff01;Midjourney图生文功能特征解析&#xff0c;玩转Describe命令&#xff0c;快速搞定AI绘画_哔哩哔哩_bilibili 1 登录 discord 2 点发现 找 midjourney 3 创建 服务器 -> 亲自创建 4 选 仅供我和我的朋友使用 5 起个 服务器名字 6 加bot 由于…

常见的旅游类软文类型分享

假期将至&#xff0c;越来越多人选择出门旅游度过假期&#xff0c;那么各大旅游品牌应该怎么让自己的旅游软文在众多品牌中脱颖而出呢&#xff1f;接下来媒介盒子就给大家分享几个最能吸引受众的旅游类型软文。 一、攻略类软文 和普通的攻略不一样&#xff0c;普通的攻略以用户…

Element--生成不定列的表格

1、对于一些场景&#xff0c;前端可能需要展示不定列数的数据&#xff1b;譬如考勤&#xff0c;可能有的人是一天一次上下班打卡&#xff0c;有的人是一天两次上下班打卡。这个时候统计就需要更具人员做不同的展示&#xff0c;不能固定在前端写死列的属性。 2、代码示例 &…

Linux命令200例:nohup用于在后台运行命令

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0…

直播APP源码搭建:核心的服务器系统

在现代科技的推动下&#xff0c;网络衍生出了各种各样的技术&#xff0c;每个技术都被应用到需要的APP上&#xff0c;直播APP源码搭建出来的APP就是其中的一个&#xff0c;然而&#xff0c;这些技术能够成功的在直播APP源码搭建的APP中稳定的为用户们提供功能与服务&#xff0c…

内网穿透实现Windows远程桌面访问Ubuntu,简单高效的远程桌面解决方案

文章目录 前言1. ubuntu安装XRDP2.局域网测试连接3.安装cpolar内网穿透4.cpolar公网地址测试访问5.固定域名公网地址 前言 XRDP是一种开源工具&#xff0c;它允许用户通过Windows RDP访问Linux远程桌面。 除了Windows RDP外&#xff0c;xrdp工具还接受来自其他RDP客户端(如Fre…

Windows环境下Springboot3+Graalvm+Idea 打包成原生镜像 踩坑

https://github.com/oracle/graal/https://github.com/graalvm/graalvm-ce-builds/releases/对应关系graalvm-ce-java17-windows-amd64-X.X.X.zipnative-image-installable-svm-java17-windows-amd64-X.X.X.jar本人使用:graalvm-ce-java17-windows-amd64-23.0.1.zipnative-imag…

华为云云耀云服务器L实例评测|华为云云耀云服务器L实例评测使用

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

Canonical 发布公告,Ubuntu可以在 Windows 10 商店找到

导读Canonical 前几天正式发布公告称&#xff0c;“Windows 10 Loves Ubuntu”&#xff0c;其 Ubuntu 16.04 LTS 在 Windows 10 商店中以应用的方式出现&#xff0c;这是继 openSUSE 及 SLES 之后&#xff0c;又一款可以从 Windows 10 商店中下载的 Linux 操作系统。 一些用户已…

Ubuntu----Linux命令-----防火墙(查看、关闭、启动)

一、查看防火墙状态 命令&#xff1a;ufw status 说明&#xff1a; 活动&#xff1a;防火墙是开启的 不活动&#xff1a;防火墙是关闭的 二、开启防火墙 命令&#xff1a;sudo ufw enable 开启防火墙后&#xff0c;可以查看防火墙状态 三、关闭防火墙 命令&#xff1a;sud…

【Python】Python运算符/部分函数对应的双下划线魔法方法

先说下Python版本&#xff1a;【Python 3.7.8】 以下用图片表格展示&#xff0c;一是防扒&#xff0c;二是没精力改成md格式。 还有就是内容肯定没有完全包含(而且也很难做到)&#xff0c;像是__reduce__与py自带模块pickle有关(pickle用于对象序列化/反序列化)、sys.getsizeo…

【Redis】如何保证Redis缓存与数据库的一致性?

文章目录 1、四种同步策略2、更新缓存还是删除缓存2.1 更新缓存2.2 删除缓存 3、先操作数据库还是缓存3.1 先删除缓存再更新数据库3.2 先更新数据库再删除缓存 4、延时双删4.1 采用读写分离的架构怎么办&#xff1f; 5、利用消息队列进行删除的补偿 1、四种同步策略 想要保证缓…

迅为RK3588在 Linux 系统中使用 NPU

下载 rknpu2 并拷贝到虚拟机 Ubuntu&#xff0c;RKNPU2 提供了访问 rk3588 芯片 NPU的高级接口。 下载地址为“iTOP-3588 开发板\02_【iTOP-RK3588 开发板】开发资料\12_NPU 使用配套资料\01_rknpu2 工具” 对于 RK3588 来说&#xff0c;Linux 平台 RKNN SDK 库文件为 librknn…

云原生Kubernetes:pod基础

目录 一、理论 1.pod 2.pod容器分类 3.镜像拉取策略&#xff08;image PullPolicy&#xff09; 二、实验 1.Pod容器的分类 2.镜像拉取策略 三、问题 1.apiVersion 报错 2.pod v1版本资源未注册 3.取行显示指定pod信息 四、总结 一、理论 1.pod (1) 概念 Pod是ku…

使用Pyarmor保护Python脚本不被反向工程

Python可读性强&#xff0c;使用广泛。虽然这种可读性有利于协作&#xff0c;但也增加了未授权访问和滥用的风险。如果未采取适当的保护&#xff0c;竞争对手或恶意攻击者可以复制您的算法和专有逻辑&#xff0c;这将对您软件的完整性和用户的信任产生负面影响。 实施可靠的安…

Stable Diffusion stable-diffusion-webui ControlNet Lora

Stable Diffusion Stable Diffusion用来文字生成图片&#xff0c;ControlNet就是用来控制构图的&#xff0c;LoRA就是用来控制风格的 。 stable-diffusion-webui 国内加速官网&#xff1a; mirrors / AUTOMATIC1111 / stable-diffusion-webui GitCode 安装参考&#xff1a…

【canal系】canal集群异常Could not find first log file name in binary log index file

这里先说明下这边使用的canal版本号为1.1.5 在描述这个问题之前&#xff0c;首先需要简单对于canal架构有个基本的了解 canal工作原理 canal 模拟 MySQL slave 的交互协议&#xff0c;伪装自己为 MySQL slave &#xff0c;向 MySQL master 发送dump 协议MySQL master 收到 dum…

const的值可不可以被更改

总结&#xff1a; 当const定义的常量是基本数据类型的时候不可以被更改 当const定义的常量是基本数据类型的时候不可以被更改 const定义的常量实际上是栈内存地址中的保存的值&#xff0c;const常量的值不可以被更改就是栈内存中保存的数据不可以被更改。基本数据类型直接存储在…