多线程(多线程案例)(续~)

news2025/4/26 2:57:25

 目录

一、单例模式

1. 饿汉模式

2. 懒汉模式

二、阻塞队列

1. 阻塞队列是什么

2. 生产者消费者模型

3. 标准库中的阻塞队列

4. 自实现阻塞队列

三、定时器

1. 定时器是什么

2. 标准库中的定时器


欢迎观看我滴上一篇关于 多线程的博客呀,直达地址:

多线程(续—解决线程不安全问题)(超详细)  感谢各位大佬的支持 💓💓💓

一、单例模式

注意:单例模式是面试中一个最常考的设计模式之一。

啥是设计模式?

设计模式就是大佬们针对一些常见的问题场景,给出的一个固定的解决套路,按照这些套路进行解决问题,就不会进行出错。

设计模式是一种思想,而非是一个固定的代码模式。

这里的单例模式就是能保证某个类在程序中只存在唯一的一个实例,而不会创建出多个实例。

单例模式存在很多种,最常见的就是 “饿汉” 和 “懒汉” 模式。

1. 饿汉模式

饿汉模式,单单从名称中就可以理解其思想,主要突出的就是——就好像一个饿汉被饿了好几天,突然看到递过来的食物一样,急切的想要的到它。

在计算机中,需要在JVM启动的时候,即类刚刚加载的时候,就创建这个类的实例。

其次,为了该类只创建一个实例,防止通过构造方法来创建多余的实例,所以我们把构造方法设置为私有。之后要想获取这个类的实例,可以通过一个普通方法来获取类中创建的这个实例。

单线程 下的来代码的实现:

public class Singleton {
    private static Singleton instance = new Singleton(); // 在类加载的时候就创建实例
    private Singleton (){  // 使用private修饰符来修饰 构造方法使其变为私有的,来避免调用构造方法

    }
    public static Singleton getInstance() { // 使用普通的方法来返回 构造的实例,需要时静态的
        return instance;
    }
}

这个饿汉模式即使在 多线程情况下,仍然是线程安全的。因为在创建这个类的时候,这个实例就已经存在了,这个实例的创建比主线程的创建都早,那么主线程只需要只读操作就可以了。

回忆前面说过线程安全问题中,如果是只读操作是不存在线程安全问题的,只有在对同一个变量进行修改的时候容易产生线程安全问题。


2. 懒汉模式

同样这个通过名称也可以理解到这个模式的思想,就是突出 ——

这个在类加载的时候不会创建实例,只有在第一次调用这个类的实例的时候才会进行创建实例。如果一直没有被调用的话,就一直不创建实例,这个实例就为null。

单线程 下的代码是实现:

public class Singleton {
    private static Singleton instance = null; 
    private Singleton (){  // 使用private修饰符来修饰 构造方法使其变为私有的,来避免调用构造方法

    }
    public static Singleton getInstance() { // 使用普通的方法来返回 构造的实例,需要时静态的
        if (instance == null) { // 判断是否被调用过
            instance = new Singleton();
        }
        return instance;
    }
}

 多线程 情况:

这个模式下的单线程情况下不会出现什么bug,但是在多线程的情况下,就可能会出现bug。这个bug可能会出现在首次创建实例的情况下,如果多个线程中同时调用 getInstance这个方法,就可能导致创建多个实例,利用图片直观的看一下:

在线程t1和线程t2创建实例的时候,线程t2的判断可能在线程t1创建实例之前,此时线程t1和线程t2都会创还能实例,那么就违背了到哪里模式的初衷。这里产生线程安全问题的原因是原子性问题。

那么通过上一个博客,解决线程安全问题的方法中,对应解决原子性的线程安全问题,可以通过加锁来进行解决,将其打包成一个原子的操作。

代码如下:

public class Singleton {
    private static Singleton instance = null;
    private Singleton (){  // 使用private修饰符来修饰 构造方法使其变为私有的,来避免调用构造方法

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

但是呢,对于上述的代码呢还存在一些问题 

1. 如果线程比较多,会产生重复加锁竞争,那就可能会产生阻塞问题。在最初调用 getInstance 的时候会存在线程安全问题,但是后序调用,就只是读取操作,并不存在线程安全问题。所以此时上锁就是多余的,上锁本身也是一个开销比较大的操作。所以就引入了双重校验锁模式

2.存在内存可见性问题和指令重排序问题导致读取 instance 的偏差。所以便引入了 volatile关键字

那么优化后的代码就是如下:

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

这里的两层判断条件虽然是一样的 判断 instance == null 的,但是它们所起的作用,却不是一样的

第一层的判断是为了在创建好实例之后,在后续获取实例的时候重复加锁,来节省系统的开销。

第二层判断是为了多个线程如果都在创建好实例之前运行,防止同时创建多个实例。


二、阻塞队列

1. 阻塞队列是什么

阻塞队列是一种特殊的队列。也遵守 “先进先出” 的原则。

阻塞队列能是一种线程安全的数据结构,并且具有以下特性:

当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素。

当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素。

阻塞队列的一个典型应用场景就是 “生产者消费者模型”。这是一种非常典型的开发模型。

2. 生产者消费者模型

注意:生产者消费者模型并不是常见的23种设计模式中的一种。

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。

就如同下述的图片一样:

 生产者消费者模型有以下两种好处:

1. 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。(削峰填谷)

这个情况最常出现在 各种购物软件的 双十一之类的活动中,当双十一的时候,服务器在同一时刻可能收到大量的支付请求,如果直接进行处理这些请求,服务器可能扛不住。

这个时候 阻塞队列 就起到作用了,在生产者和消费之间放入一个阻塞队列,把支付请求放入到阻塞队列中,让消费者线程慢慢的进行处理每一个支付请求。

2. 阻塞队列能使生产者和消费者之间 解耦

比如在餐厅吃饭,客户就是消费者,后厨就是生产者,消费者在服务员(阻塞队列)这里进行点餐,服务员把菜单(请求) 给到后厨,后厨就行返回做好的菜(响应)。这样呢 客户 不去关心这个菜是谁做的,后厨不用关心这个菜是谁点的。这样就做到了 解耦。


3. 标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列。如果我们需要在一些程序中使用阻塞队列,直接使用标准库中的即可。
• BlockingQueue 是一个接口。真正实现的类是 LinkedBlockingQueue/ArrayBlockingQueue/PriorityBlockingQueue。

• put 方法用于阻塞式的入队列,ake 用于阻塞式的出队列。
• BlockingQueue 也有offer,poll,peek 等方法,但是这些方法不带有阻塞特性。

public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        // ⼊队列
        queue.put("abc");
        // 出队列. 如果没有 put 直接 take, 就会阻塞.
        String elem = queue.take();
    }

使用标准库进行实现 生产者和消费者模型:

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

        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int value = blockingQueue.take(); // take 阻塞式的出队列
                    System.out.println("消费元素: " + value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者");
        customer.start();

        Thread producer = new Thread(() -> {
            Random random = new Random();
            while (true) {
                try {
                    int num = random.nextInt(1000);
                    System.out.println("⽣产元素: " + num);
                    blockingQueue.put(num); // put 阻塞式的入队列
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "⽣产者");
        producer.start();

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

4. 自实现阻塞队列

• 通过 “循环队列” 的方式实现。

• 使用 synchronized 进行加锁控制。

• put 插入元素的时候,判定如果队列满了,就进行 wait。(注意,要在循环中进行 wait。被唤醒时不一定队列就不满了,因为同时可能唤醒多个线程)。

• take 取出元素的时候,判定如果队列为空,就进行 wait。(也是循环 wait)

public class MyBlockingQueue {
    private int[] items = new int[1000];
    private volatile int size = 0;
    private volatile int head = 0;
    private volatile int tail = 0;

    public void put(int value) throws InterruptedException {
        synchronized (this) {
            // 此处最好使⽤ while.
            // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
            // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能⼜已经队列满了
            // 就只能继续等待
            while (size == items.length) {
                wait();
            }
            items[tail] = value;
            tail = (tail + 1) % items.length;
            size++;
            notifyAll();
        }
    }

    public int take() throws InterruptedException {
        int ret = 0;
        synchronized (this) {
            while (size == 0) {
                wait();
            }
            ret = items[head];
            head = (head + 1) % items.length;
            size--;
            notifyAll();
        }
        return ret;
    }

    public synchronized int size() {
        return size;
    }
    // 测试代码
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue blockingQueue = new MyBlockingQueue();

        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int value = blockingQueue.take();
                    System.out.println(value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者");
        customer.start();
        Thread producer = new Thread(() -> {
            Random random = new Random();
            while (true) {
                try {
                    blockingQueue.put(random.nextInt(10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");
        producer.start();

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

三、定时器

1. 定时器是什么

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

定时器是一种实际开发中非常常用的组件。

比如网络通信中,如果对方 500ms 内没有返回数据,则断开连接尝试重连。

类似这样的场景就需要使用定时器。

2. 标准库中的定时器

• 标准库中提供了一个Timer类。Timer 类的核心方法为 schedule。

• schedule 包含两个参数。第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(单位为毫秒)。

public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello");
            }
        },3000);
    }

 这次的分享到这里就结束了,感觉文章不错的话,期待你的一键三连哦,你的鼓励就是我的动力,让我们一起加油,顶峰相见。拜拜喽~~我们下次再见💓💓💓💓

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

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

相关文章

一个判断A股交易状态的python脚本

最近在做股票数据相关的项目&#xff0c;需要用到判断某一天某个时刻A股的状态&#xff0c;比如休市&#xff0c;收盘&#xff0c;交易中等&#xff0c;发动脑筋想了一下&#xff0c;这个其实还是比较简单的&#xff0c;这里我把实现方法分享给大家。 思路 当天是否休市 对于某…

闪记(FlashNote):让灵感快速成文的轻量级笔记工具

闪记&#xff08;FlashNote&#xff09;&#xff1a;让灵感快速成文的轻量级笔记工具 你是否经常遇到这样的情况&#xff1a;桌面上放了一大堆的新建123.txt&#xff0c;想记录一个想法&#xff0c;应该是一键开个一个快捷键然后瞬间记录就自动保存了&#xff0c;现在的很多笔记…

《大模型部署》——ollama下载及大模型本地部署(详细快速部署)

ollama Ollama 是一款开源跨平台的大语言模型&#xff08;LLM&#xff09;运行工具&#xff0c;旨在简化本地部署和管理 AI 模型的流程。 下载ollama 进入官网下载https://ollama.com/ 选择需要的系统下载 下载完成后直接进行安装 下载大模型 选择想要部署的模型&#…

Geotools结合SLD实现矢量中文标注下的乱码和可用字体解析

目录 前言 一、需求溯源 1、原始的SLD渲染 2、最初的效果 二、问题修复 1、还是字符编码 2、如何选择可用的字体 3、如何查看支持的字体库 三、总结 前言 随着地理信息系统&#xff08;GIS&#xff09;技术的不断发展&#xff0c;矢量数据的可视化和标注成为了地理信息展…

基于Python与CATIA V5的斐波那契螺旋线自动化建模技术解析

引言 斐波那契螺旋线&#xff08;Fibonacci Spiral&#xff09;作为自然界广泛存在的黄金比例曲线&#xff0c;在工业设计、产品造型、机械工程等领域具有重要应用价值。本文将以Python控制CATIA V5进行参数化建模为例&#xff0c;深入解析三维CAD环境中复杂数学曲线的自动化生…

动态规划(11.按摩师)

题目链接&#xff1a;面试题 17.16. 按摩师 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a; 状态表示&#xff1a; 对于简单的线性 dp &#xff0c;我们可以⽤「经验 题⽬要求」来定义状态表⽰&#xff1a; 以某个位置为结尾&#xff0c;巴拉巴拉&#xff1b;…

CentOS下安装Docker,Docker下安装JDK\MYSQL\REDIS\NGINX

先用VM安装好Centos8.5&#xff0c;可以选择安装迷你版&#xff0c;我安装的是UI版。 然后用MobaXterm_Portable_v23.0_cn连上去&#xff0c;互访成功就可以往下操作。 1. 修改文件&#xff1a;就是要把之前的mirror替换成现在的vault cd /etc/yum.repos.d/sed -i s/mirrorl…

demo.launch(inbrowser=True, share=True)无法生成共享网址

Gradio 的共享功能无法正常工作&#xff0c;原因是缺少一个名为 frpc_windows_amd64_v0.3 用到代码 app.demo.launch(show_errorTrue, inbrowserTrue, shareTrue) show_errorTrue&#xff1a;这个参数的作用是当应用在启动过程中出现错误时&#xff0c;会显示错误信息。这对于调…

翻译: 人工智能如何让世界变得更美好二

Basic assumptions and framework 基本假设和框架 To make this whole essay more precise and grounded, it’s helpful to specify clearly what we mean by powerful AI (i.e. the threshold at which the 5-10 year clock starts counting), as well as laying out a fram…

【vue】editor富文本输入全英文,谷歌浏览器:元素不会自动换行bug

【vue】editor富文本输入全英文&#xff0c;谷歌浏览器&#xff1a;元素不会自动换行bug 解决方案&#xff1a;给元素一个宽度 100% .editor {width: 100%; }

# OpenCV实现人脸与微笑检测:从图像到视频的实战应用

OpenCV实现人脸与微笑检测&#xff1a;从图像到视频的实战应用 在计算机视觉领域&#xff0c;人脸检测和微笑检测是两个非常有趣且实用的任务。它们广泛应用于智能监控、社交媒体分析、人机交互等多个场景。本文将通过两个代码示例&#xff0c;详细介绍如何使用OpenCV实现人脸…

Kubernetes可视化面板——KubePi(Kubernetes Visualization Panel - kubepi)

Kubernetes可视化管理面板——KubePi 在云计算和容器化的大潮下&#xff0c;Kubernetes 已成为管理容器集群的事实标准。然而&#xff0c;面对复杂的集群管理和运维工作&#xff0c;一个直观、易用的可视化工具显得至关重要。KubePi 正是为此而生——一款专为简化 Kubernetes …

【区块链安全 | 第二十三篇】单位和全局可用变量(一)

文章目录 单位和全局可用变量&#xff08;Units and Globally Available Variables&#xff09;以太单位&#xff08;Ether Units&#xff09;时间单位&#xff08;Time Units&#xff09;保留关键字 单位和全局可用变量&#xff08;Units and Globally Available Variables&am…

权重参数矩阵

目录 1. 权重参数矩阵的定义与作用 2. 权重矩阵的初始化与训练 3. 权重矩阵的解读与分析 (1) 可视化权重分布 (2) 统计指标分析 4. 权重矩阵的常见问题与优化 (1) 过拟合与欠拟合 (2) 梯度问题 (3) 权重对称性问题 5. 实际应用示例 案例1&#xff1a;全连接网络中的…

【现代深度学习技术】现代卷积神经网络06:残差网络(ResNet)

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…

《异常检测——从经典算法到深度学习》30. 在线服务系统中重复故障的可操作和可解释的故障定位

《异常检测——从经典算法到深度学习》 0 概论1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法3 基于One-Class SVM的异常检测算法4 基于高斯概率密度异常检测算法5 Opprentice——异常检测经典算法最终篇6 基于重构概率的 VAE 异常检测7 基于条件VAE异常检测8 Donut: …

nut-ui下拉选的实现方式:nut-menu

nut-ui下拉选的实现方式&#xff1a;nut-menu 官方文档&#xff1a;https://nutui.jd.com/h5/vue/4x/#/zh-CN/component/menu 案例截图&#xff1a; nut-tab选项卡组件实现&#xff1a; 官方组件地址&#xff1a;https://nutui.jd.com/h5/vue/4x/#/zh-CN/component/tabs nut…

鸿蒙NEXT小游戏开发:扫雷

1. 引言 本文将介绍如何使用鸿蒙NEXT框架开发一个简单的扫雷游戏。通过本案例&#xff0c;您将学习到如何利用鸿蒙NEXT的组件化特性、状态管理以及用户交互设计来构建一个完整的游戏应用。 2. 环境准备 电脑系统&#xff1a;windows 10 工程版本&#xff1a;API 12 真机&…

LangChain4j 入门(二)

LangChain 整合 SpringBoot 下述代码均使用 阿里云百炼平台 提供的模型。 创建项目&#xff0c;引入依赖 通过 IDEA 创建 SpringBoot 项目&#xff0c;并引入 Spring Web 依赖&#xff0c;SpringBoot 推荐使用 3.x 版本。 引入 LangChain4j 和 WebFlux 依赖 <!--阿里云 D…

npm i 失败

当npm i 失败 且提示下面的错误 尝试降低npm 的版本 npm install npm6.14.15 -g