多线程(代码案例: 单例模式, 阻塞队列, 生产者消费者模型,定时器)

设计模式是什么

类似于棋谱一样的东西
计算机圈子里的大佬为了能让小菜鸡的代码不要写的太差
针对一些典型的场景, 给出了一些典型的解决方案
这样小菜鸡们可以根据这些方案(ACM里面叫板子, 象棋五子棋里叫棋谱, 咱这里叫 设计模式), 略加修改, 这样代码再差也差不到哪里去 …

单例模式

单例模式 => 单个对象(实例)
在有些场景中, 有些特定的类, 只允许创建出一个实例, 不应该创建出多个实例
单例模式就是巧用 Java 的语法规则, 达成了某个类只能被创建出一个实例 (单线程多线程下都只能创建出一个实例)


单例模式的实现 – 饿汉模式

// 单例模式 - 饿汉模式
class Singleton {
	// 此处先创建出一个实例 (类加载阶段就创建出来了)
    private static Singleton singleton = new Singleton();
	
	// 如果使用该唯一实例, 统一通过 Singleton.getSingleton() 方法使用
    public static Singleton getSingleton() {
        return singleton;
    }
    
    // 将构造方法设置为私有, 即不可再创建实例
    private Singleton() {}
}
public class Main{
    public static void main(String[] args) {
        Singleton s = Singleton.getSingleton();
        Singleton ss = Singleton.getSingleton();
        System.out.println("s == ss : " + (s==ss));
    }
}

运行结果

在这里插入图片描述


单例模式的实现 – 懒汉模式

// 单例模式 - 懒汉模式
class SingletonLazy {
    volatile private static SingletonLazy singletonLazy = null; //volatile 保证内存可见性, 即

    public static SingletonLazy getInstance() {
        if(singletonLazy == null) { //判断是否要加锁
            synchronized (SingletonLazy.class) {
                if(singletonLazy == null) { //判断是否要创建对象
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }

    private SingletonLazy() {}
}

public class Main {
    public static void main(String[] args) {
        SingletonLazy s = SingletonLazy.getInstance();
        SingletonLazy ss = SingletonLazy.getInstance();
        System.out.println("s == ss : " + (s == ss));
    }
}

运行结果
在这里插入图片描述

这段代码挺有意思的, 其中值得关注的点挺多
双层 if :

  1. 外层 if 判断里面的 sychronized ,加锁操作是否要执行, 如果 singletonLazy 对象已存在, 就不用再进行加锁,创建对象的操作了 (sychronized 操作比 if 操作消耗要多的多, 如果不追求性能, 外层 if 可以不要)
  2. 内层 if 判断是否要创建对象, 如果 singletonLazy 未存在, 就创建. 多线程环境下可能会出现同时创建多个对象的情况 (不满足单例模式的要求), 因此我们对内层 if 判断及里面的创建对象进行加锁, 由于是单例模式 (只有一个类对象), 因此直接对该类对象加锁就好

volatile 保证内存可见性以及禁止指令重排序, 这也是对内层 if :if(singletonLazy == null)的限制, 防止多线程环境下出现 “类似脏读的问题”

警告: sychronized 能够保证原子性, 但是 sychronized 能否保证 内存可见性, 这里是存疑的 (有的资料说 sychronized 不能保证内存可见性, 因此保险起见, 这里是 volatile 也加上的 …)


懒汉模式和饿汉模式的区别

懒汉模式就是不直接创建对象(实例), 什么时候用到, 才创建对象(实例)
饿汉模式就是直接创建对象, 需要用的时候可以直接用, 不用再等待实例的创建等过程


阻塞队列

阻塞队列, 也是队列, 因此具有特点 – 先进先出, 后进后出
阻塞:

  1. 如果队列为空,执行出队列操作, 就会阻塞, 直到其他线程往队列里添加元素 (队列不空)
  2. 如果队列已满,执行入队列操作, 也会阻塞, 直到其他线程从队列里取走元素 (队列不满)

Java 标准库实现的阻塞队列

public class Main{
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();

        blockingQueue.put("hello");
        System.out.println(blockingQueue.take());
        // 其实在多线程环境下使用阻塞效果更明显, 这里只是单纯当初队列来用了
    }
}

运行结果
在这里插入图片描述


手写阻塞队列

不考虑泛型, 单纯考虑队列中元素为 Integer 类型, 使用数组实现循环队列

// 手写阻塞队列 (不考虑泛型, 单纯的 Integer 类型, 循环数组实现)
class MyBlockingQueue {
    private int[] items = new int[1000];
    private int head = 0; //头指针
    private int tail = 0; //尾指针
    private int size = 0; //已有元素的数量

    // 入队列
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            while(size == items.length) { // while 是精髓, 如果 put 操作被唤醒后, 又因某些原因队列又满了, 这里的 while 可以达到多次判断的效果(这也是 Java 标准库阻塞队列的写法)
                this.wait();
            }
            items[tail] = value;
            tail++;
            if(tail >= items.length) tail = 0;
            size++;

            this.notify();
        }
    }

    // 出队列
    public Integer take() throws InterruptedException {
        int val;
        synchronized (this) {
            while (this.size == 0) { //这个 while 的作用同上
                this.wait();
            }
            val = items[head];
            head++;
            if(head >= items.length) {
                head = 0;
            }
            size--;

            this.notify();
        }
        return val;
    }
}

public class Main{
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue();
        Thread t1 = new Thread(() -> {
            try {
                System.out.println(queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                queue.put(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        Thread.sleep(3000); //这里是为了让 t1线程先执行, 让队列阻塞掉 (如果 t2 先执行, 那么 t1 执行的时候, 队列内就会有数据, 就不会产生阻塞的效果了) (不加 Thread.sleep(3000) 的话, t1 和 t2 谁先执行, 是不一定的[该死的随机调度, 抢占式执行 ...])
        t2.start();

        t1.join();
        t2.join();
    }
}

运行结果
在这里插入图片描述


生产者消费者模型

作用

  1. 实现发送方和接收方的解耦合
  2. 削峰填谷

其实就是阻塞队列的简单使用

// 简单实现生产者消费者模型
public class Main {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        // 消费者
        Thread customer = new Thread(() -> {
            while(true) {
                try {
                    System.out.println("消费元素 " + blockingQueue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 生产者
        Thread producer = new Thread(() -> {
            int x = 0;
            while(true) {
                try {
                    blockingQueue.put(x);
                    System.out.println("生产元素 " + x++);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        customer.start();
        producer.start();

        customer.join();
        producer.join();
    }
}

运行结果
在这里插入图片描述

这里代码未结束, 而是一直执行下去, 并且代码逻辑是先生产, 再消费


定时器

作用

让一个任务在指定时间运行


Java 标准库提供了 “定时器”

// 定时器的简单使用
public class Main{
    public static void main(String[] args) {
        System.out.println("程序启动");

        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器1 任务执行");
            }
        }, 1000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器2 任务执行");
            }
        }, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器3 任务执行");
            }
        }, 3000);
    }
}

运行结果
在这里插入图片描述


手写一个定时器

定时器的核心:

  1. 有一个扫描线程, 当 任务时间到 的时候执行任务
  2. 有一个数据结构来被注册任务

数据结构使用阻塞优先级队列 (可保证线程安全), 也可根据任务的执行时间进行排序
每个任务包含两部分 (任务的内容, 执行时间)

// 手写定时器

// 任务
class MyTask implements Comparable<MyTask> {
    // 要执行的任务
    private Runnable runnable;
    // 要执行的时间
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    // 获取任务的执行时间
   public long getTime() {
       return time;
   }

   // 执行任务
   public void run() {
       runnable.run();
   }

   // 这里定义了 阻塞优先级队列 的排序规则, 别死记, 当场试一试
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

// 定时器
class MyTimer {
    // 扫描线程
    private Thread t = null;

    // 使用阻塞优先队列, 来保存任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public MyTimer() {
        // 只要调用了构造方法,即创建了定时器, 扫描器就会一直扫任务队列, 看是否有任务需要执行
        t = new Thread(() -> {
            while(true) {
                try {
                    synchronized (this) {
                        // 取出队首元素, 判断是否任务时间已到
                        MyTask myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if(curTime < myTask.getTime()) {
                            // 任务时间未到, 丢回任务队列
                            queue.put(myTask);
                            this.wait(myTask.getTime() - curTime); //这里的设计很巧妙, wait 既保证可以在当前队列最早的任务可以及时执行, 当新的任务来临时 notify 也可以将扫描线程唤醒, 也防止了扫描线程一直扫占用 CPU 资源(忙等)
                        } else {
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long after) {
        MyTask myTask = new MyTask(runnable, (after + System.currentTimeMillis()) );
        queue.put(myTask);
        synchronized (this) {
            this.notify(); //这里唤醒的作用是, 如果当前塞进去的任务的执行时间要先于当前队列中任务执行时间最近的那个, 即可以优先执行本任务
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello MyTimer!");
            }
        }, 2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello MyTimer2!");
            }
        }, 3000);
    }
}

运行结果
在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

官方安装配置要求服务器最低2核4G

官方安装配置要求服务器至少2核、4G。 如果服务器低于这个要求&#xff0c;就没有必要安装&#xff0c;因为用户体验超级差。 对于服务器CPU来说&#xff0c;建议2到4核就完全足够了&#xff0c;太多就浪费了&#xff0c;但是内存越大越好&#xff0c;最好是4G以上。 如果服务器…

数据库 | Mysql - [binlog]

INDEX 1 什么是 binlog2 作用3 数据恢复4 主从复制 1 什么是 binlog Mysql server 的日志文件 自动开启 2 作用 数据恢复主从复制 3 数据恢复 实际场景 01.00&#xff1a;数据全量备份08.00&#xff1a;数据丢失&#xff08;比如被人误删&#xff09;09.00&#xff1a;故…

贪心问题题目集一(代码 注解)

目录 介绍&#xff1a; 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 题目八&#xff1a; 介绍&#xff1a; 贪心算法是一种特殊的算法思想&#xff0c;它在每一步选择中都采取…

二叉树的初步学习和顺序结构实现

当我们学完顺序表、链表、栈和队列的时候&#xff0c;我们就要开始学习树了。树对于以后的学习有非常大的帮助&#xff0c;尤其是排序。好了&#xff0c;开始我们的学习吧。 1.树的概念及结构 1.1树的结构 树结构是一种非线性结构。它是由n&#xff08;n>0&#xff09;个…

ISIS接口MD5 算法认证实验简述

默认情况下&#xff0c;ISIS接口认证通过在ISIS协议数据单元&#xff08;PDU&#xff09;中添加认证字段&#xff0c;例如&#xff1a;MD5 算法&#xff0c;用于验证发送方的身份。 ISIS接口认证防止未经授权的设备加入到网络中&#xff0c;并确保邻居之间的通信是可信的。它可…

【教学类-34-10】20240313 春天拼图(Midjounery生成线描图,4*4格拼图块)(AI对话大师)

作品展示&#xff1a; 背景需求&#xff1a; 利用华文彩云空心字&#xff08;粗胖字体。凑满9个拼图&#xff09;制作了3*3的拼图块 【教学类-34-09】20240310华文彩云学号拼图&#xff08;3*3格子浅灰底图 深灰拼图块&#xff09;&#xff08;AI对话大师&#xff09;-CSDN博…

唯一约束

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 唯一约束 唯一约束的特点是在某一个列上的内容不允许出现重复。 例如&#xff0c;现在要收集用户的信息&#xff0c;假设包含编号&#xff08;mid&#xff09;、姓名&…

【vue在主页中点击主页面如何弹出一个指定某个页面的窗口】

【vue在主页中点击主页面跳转到某个页面的操作完整过程】 1.首先在主页面中加入一个卡槽用于展示弹出的窗口 代码如下&#xff1a; <el-dialog :visible.sync"dialogVisible1" :close-on-click-modal"false" :title"title" class"dial…

Docker出现容器名称重复如何解决

假如你的重复容器名称是mysql5 删除已存在的容器&#xff1a;如果你不再需要那个已经存在的名为“mysql5”的容器&#xff0c;你可以删除它。使用下面的命令&#xff1a; docker rm -f mysql5这条命令会强制删除正在运行的容器。一旦容器被删除&#xff0c;你就可以重新使用这个…

Git全套教程一套精通git.跟学黑马笔记

Git全套教程一套精通git.跟学黑马笔记 文章目录 Git全套教程一套精通git.跟学黑马笔记1.版本管理工具概念2. 版本管理工具介绍2.1版本管理发展简史(维基百科)2.1.1 SVN(SubVersion)2.1.2 Git 3. Git 发展简史4. Git 的安装4.1 git 的下载4.2 安装4.3 基本配置4.4 为常用指令配置…

ElasticSearch之Nested对象

写在前面 本文看下es的nested嵌套对象相关内容。 1&#xff1a;es用了啥范式&#xff1f; 在关系型数据库中定义了6大数据库范式,即1&#xff0c;2&#xff0c;3&#xff0c;BC&#xff0c;4&#xff0c;5的NF&#xff08;normal form&#xff09;,分别如下&#xff1a; 1N…

快速去除或提取视频中的任何声音,你学会了吗

怎么提取视频中的音频&#xff1f;本文将向您介绍多个简单而有效的方法&#xff0c;帮助您轻松掌握如何提取视频中的音频。无论您是视频编辑新手还是经验丰富的用户&#xff0c;这些建议都将为您提供多样选择&#xff0c;满足各种需求。 方法一&#xff1a;使用在线转换工具提取…

一维差分(模板)

差分是前缀和的逆运算&#xff0c;对于一个数组a&#xff0c;其差分数组b的每一项都是a [ i ]和前一项a [ i − 1 ]的差。 **注意&#xff1a;**差分数组和原数组必须分开存放&#xff01;&#xff01;&#xff01;&#xff01; #include <iostream> using namespace s…

python爬虫-AES.CBS加密案例(mmz批量爬取)

下载mmz本页数据 批量下载请看主页&#xff01;&#xff01;&#xff01; 代码&#xff1a; import requests from Crypto.Cipher import AES import base64cookies {PHPSESSID: 48nu182kdlsmgfo2g7hl6eufsa,Hm_lvt_6cd598ca665714ffcd8aca3aafc5e0dc: 1710568549,SECKEY_A…

deepseek-coder模型量化

1 简介 DeepSeek-Coder在多种编程语言和各种基准测试中取得了开源代码模型中最先进的性能。 为尝试在开发板进行部署&#xff0c;首先利用llama.cpp对其进行量化。 2 llama.cpp安装 git clone之后进入文件夹make即可&#xff0c;再将依赖补全pip install -r requirements.tx…

可视化图表:南丁格尔玫瑰图,来自历史上最著名的护士。

Hi&#xff0c;我是贝格前端工场的老司机&#xff0c;本文分享可视化图表设计的南丁格尔玫瑰图设计&#xff0c;欢迎老铁持续关注我们。 一、南丁格尔与玫瑰图 南丁格尔&#xff08;Florence Nightingale&#xff0c;1820年-1910年&#xff09;是一位英国护士和统计学家&…

江大白 | 万字长文,深度全面解读PyTorch内部机制,推荐阅读!

本文来源公众号“江大白”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;万字长文&#xff0c;深度全面解读PyTorch内部机制&#xff0c;推荐阅读&#xff01; 以下文章来源于知乎&#xff1a;人工智能前沿讲习 作者&#xff1a…

2024.4.17周报

目录 摘要 Abstract 文献阅读&#xff1a;耦合时间和非时间序列模型模拟城市洪涝区洪水深度 现有问题 提出方法 创新点 XGBoost和LSTM耦合模型 XGBoost算法 ​编辑 LSTM&#xff08;长短期记忆网络&#xff09; 耦合模型 研究实验 数据集 评估指标 研究目的 洪水…

海外媒体宣发套餐推广攻略轻松提升曝光率标题文章-华媒舍

在当今数字化时代&#xff0c;推广和宣传对于企业和个人都变得至关重要。但是如何有效地提高曝光率&#xff0c;吸引更多的目标受众成为了一个挑战。本文介绍一种名为《海外媒体宣发套餐推广攻略》的方法&#xff0c;通过使用该套餐&#xff0c;您可以轻松地提高宣传品曝光率&a…