多线程案例 - 单例模式

news2024/11/30 0:34:14

v2-b6f7ee5f6c7a9e66581bda987710eb4f_b0213

单例模式 ~~ 单例模式是常见的设计模式之一

什么是设计模式

你知道象棋,五子棋,围棋吗?如果,你想下好围棋,你就不得不了解一个东西,”棋谱”,设计模式好比围棋中的 “棋谱”.
在棋谱里面,大佬们,把一些常见的对局场景,都给推演出来了,照着棋谱来下棋,基本上棋力就不会差到哪里去.
同理,软件开发中也有很多常见的 “问题场景”, 针对这些问题场景, 大神们总结出了一些固定的套路, 按照这个套路来实现代码, 写的代码就不会太差.
设计模式就是针对一些典型的场景,给出了一些典型的解决方案.

单例模式

单例模式 => 单个实例(对象)
~~ 通过巧用Java的现有语法,达成了某个类只能被创建出一个实例这样的效果,当我们不小心创建了多个实例,就会编译报错.

场景: 很多场景广泛,比如JDBC中DataSource这样的类,其实就非常适合于使用单例模式.

单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种
注: 其实在Java里实现单例模式的方式有很多种,只是这两种最常见.

饿汉模式

类加载阶段,就把实例创建出来了(类加载是比较靠前阶段),这种效果,就给人一种"特别急切”的感觉,就像一个饿了很久的人,看到吃的,就会很急切,这种感觉就给它起了一个形象的名字,叫做“饿汉模式”,还有一个原因就是与后文讲解的“懒汉模式”相对应.

class Singleton {
    // 在此处, 先把这个实例给创建出来了
    private static Singleton instance = new Singleton();
    // 被 static 修饰的 Singleton 这个属性和实例无关,而是和类有关
    /*
     * java 代码中的每个类,都会在编译完成后得到.class 文件.
     * JVM 运行是就会加载这个 .class 文件读取其中的二进制指令,并且在内存中
     * 构造出对应的类对象.(形如 Singleton.class) => 
     * */
    /*
     * 由于类对象 在一个 java 进程里,只是有唯一一份的
     * 因此类对象内部的类属性也是唯一一份了
     * */

    // 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取对象
    public static Singleton getInstance() {
        return instance;
    }

    // 为了避免 Singleton 类不小心被复制出多份来.
    // 把构造方法设为 private, 在类外面,就无法通过 new 的方式来创建这个 Singleton 实例了
    private Singleton() {
    }
}

如何保证实例唯一的

  1. static这个操作,是让当前instance属性是类属性了.
    类属性是在类对象上的,类对象又是唯一实例的(只是在类加载阶段被创建出一个实例)
    • 注: 类属性和类对象是一 一对应的,即类对象如果是多个了,类属性也是就有多份了,此时类对象就不是单例的了.
  2. 构造方法是设为private.外面的代码中无法new.

类加载阶段

运行一个Java程序,就需要让Java进程能够找到并读取对应的.class文件,就会读取文件内容,并解析,构造成类对象…这一系列的过程操作,称为类加载.

懒汉模式的实现

这个实例并非是类加载的时候创建了,而是真正第一次使用的时候,才去创建(如果不用,就不创建了 => “懒”).
注: 在计算机中,懒,往往是褒义词,勤快,才是贬义词 ~~ 从"效率"上考虑,懒汉模式比饿汉模式更胜一筹!!!

懒汉模式-单线程版

class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy() {
    }
}

上述写的饿汉模式和懒汉模式,如果在多线程环境下调用getInstance,是否是线程安全的?

image-20231001135552693

if (instance == null) { instance = new SingletonLazy(); } return instance;

if (instance == null) {
       instance = new SingletonLazy();
   }
   return instance;

image-20231001142343309

刚才线程安全问题,本质是读,比较和写这三个操作不是原子的,这就导致了t2读到的值可能是t1还没来得及写的(脏读)

懒汉模式-多线程版

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

注: 加锁了之后,确保了此时的读操作和修改操作是一个整体.
image-20231001151835949

懒汉模式-多线程版(改进)

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

上述代码就导致每次getlnstance都需要加锁(加锁操作是有开销的).
问题来了: 真的需要每次加锁吗?
是不需要的,这里的加锁只是在new出对象之前加上,是有必要的.
一旦对象new完了,后续调用getlnstance,此时instance的值一定是非空的,因此就会直接触发return.
相当于一个是比较操作,一个是返回操作,这两个操作都是读操作,此时不加锁也是OK的

解决: 基于上述讨论,就可以给上面的代码加上一个判定:
如果对象还没创建,才加锁;
如果对象已经创建过了,就不加锁了.

public static SingletonLazy getInstance() {
    if (instance == null) { // 此处不再是无脑加锁了而是满足了特定条件之后,才真正加锁.
        synchronized (SingletonLazy.class) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
     }
    return instance;
}

解析: 如果这两个条件中间没有加锁,连续两个相同的 if 是没意义的.
但是有了加锁,就不一定了,加锁操作可能会引起线程阻塞.当执行到锁结束,再执行到第二个if 的时候,
第二个 if 和第一个 if 之间可能已经隔了很久的时间,沧海桑田.
程序的运行内部的状态,这些变量的值,都可能已经发生很大改变了.
注: 第一个 if 条件 负责判定是否要加锁, 第二个 if 条件负责判定是否要创建对象.这两个 if 条件的目的是完全不同的,只不过由于巧合,代码是一样的.

上述懒汉模式的代码,还有内存可见性问题&指令重排序问题待解决!

可见性问题
假设有很多线程,都去进行getInstance,这个时候,是否就会有被优化的风险呢?
(只有第一次读才是真正读了内存,后续都是读寄存器/cache)

指令重排序问题

instance new Singleton();
拆分成三个步骤:
1.申请内存空间.
2.调用构造方法,把这个内存空间初始化成一个合理的对象.
3.把内存空间的地址赋值给 instance 引用.

正常情况下,是按照123这个顺序来执行的,但是编译器还有一手操作,指令重排序为了提高程序效率,调整代码执行顺序,123这个顺序就可能变成132.
如果是单线程,123和132没有本质区别,但是多线程环境下,就会存在问题!!!
假设t1是按照132的步骤执行的.t1执行到13之后,执行2之前,被切出CPU, t2 来执行(当t1执行完13之后, 在t2看来,此处的引用就非空了), 此时此刻, t2就相当于直接返回了instance引用并且可能会尝试使用引用中的属性. 但是由于t1中的2操作还没执行完呢, t2拿到的是非法的对象,还没构造完成的不完整的对象.

volatile

volatile有两个功能:

  1. 解决内存可见性
    2.禁止指令重排序

完全体的单例模式(懒汉模式)代码

class SingletonLazy {
    private volatile static SingletonLazy instance = null;// 1

    public static SingletonLazy getInstance() {
        if (instance == null) {// 2
            synchronized (SingletonLazy.class) {// 3
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() {
    }
}

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

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

相关文章

面试必考精华版Leetcode437. 路径总和 III

题目: 代码(首刷看解析,暴力法): class Solution { public:long rootSum(TreeNode* root,long targetSum){if(!root) return 0;long res0;if(root->valtargetSum){res;} resrootSum(root->left,targetSum-root-…

2022年9月及10月

9月 1.Halcon12的HObject和Hobject halcon12 可以用HObject,也可以用Hobject,用法都一样 包括HalconCpp.h 如果附加目录中: C:\Program Files\MVTec\HALCON-12.0\include\halconcpp\ 在前面,则用 HalconCpp::HObject 如果附加目录…

【论文阅读】DiffusionDet: Diffusion Model for Object Detection

原文链接:https://arxiv.org/abs/2211.09788 1. 引言 过去的目标检测方法依赖手工设计的候选对象(如滑动窗口、区域提案、锚框和参考点);或是依赖可学习的物体查询。   本文使用更加简单的方法,随机初始化边界框&am…

防火墙基础之H3C防火墙和三层交换机链路聚合的配置

H3C防火墙和三层交换机链路聚合的配置 原理概述: 防火墙(英语:Firewall)技术是通过有机结合各类用于安全管理​与筛选的软件和硬件​设备,帮助计算机网络于其内、外网之间构建一道相对隔绝的保护屏障,以保…

Docker从认识到实践再到底层原理(七)|Docker存储卷

前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

如何让你网站统计的更加精准?

引言 最近对比了自己网站在Cloudflare Analytics和51.la统计的数据,结果发现数值差距的比较大,这是为什么? 经过了摸索,发现了以下几个情况: 广告插件的拦截,大部分广告插件都会拦截网站统计&#xff0c…

select实现服务器并发

select的TCP服务器代码 #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/select.h> #include…

【教学类-36-10】20230908方脸爷爷和圆脸奶奶(midjounery-niji)(中班:《我爱我家》数:连线、涂色)

背景需求&#xff1a; 领导们鼓动我去参加上海市高级职称评审&#xff08;科研成果比较多&#xff09;&#xff0c;为下一轮保教主任评高级“探探路”。虽然自我感觉道行浅薄&#xff0c;无缘高级&#xff0c;但领导给机会&#xff0c;自然要参与一下&#xff0c;努力了解整个…

10.1select并发服务器以及客户端

服务器&#xff1a; #include<myhead.h>//do-while只是为了不让花括号单独存在&#xff0c;并不循环 #define ERR_MSG(msg) do{\fprintf(stderr,"%d:",__LINE__);\perror(msg);\ }while(0);#define PORT 8888//端口号1024-49151 #define IP "192.168.2.5…

10月1日作业

汇编指令合集 用select实现服务器并发代码 #include<myhead.h> #define IP "192.168.0.106" #define PORT 8888int main(int argc, const char *argv[]) {//新建套接字文件int sfd socket(AF_INET, SOCK_STREAM, 0);if(sfd < 0){ERR_MSG("socket&quo…

imgui开发笔记<4>、image-slider online

在线滑条二值化。 // // Created by sry on 2021/6/30. //#include"imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include <stdio.h> // Initialize with gl3wInit() #include<GL/gl3w.h> // Include …

【MATLAB第78期】基于MATLAB的VMD-SSA-LSTM麻雀算法优化LSTM时间序列预测模型

【MATLAB第78期】基于MATLAB的VMD-SSA-LSTM麻雀算法优化LSTM时间序列预测模型 一、LSTM data xlsread(数据集.xlsx);% [x,y]data_process(data,15);%前15个时刻 预测下一个时刻 %归一化 [xs,mappingx]mapminmax(x,0,1);xxs; [ys,mappingy]mapminmax(y,0,1);yys; %划分数据 n…

Android进阶——Handler底层fd监听之epoll机制

文章大纲 引言一、从网卡接收数据说起二、如何知道接收了数据&#xff1f;三、进程阻塞为什么不占用cpu资源&#xff1f;四、那么阻塞的原理是什么&#xff1f;1、工作队列2、等待队列3、唤醒进程 五、内核接收网络数据全过程六、同时监视多个socket的简单方法七、epoll的设计思…

Redis入门到精通——00数据类型

1、String 1.1、介绍 String 是最基本的 key-value 结构&#xff0c;key 是唯一标识&#xff0c;value 是具体的值&#xff0c;value其实不仅是字符串&#xff0c; 也可以是数字&#xff08;整数或浮点数&#xff09;&#xff0c;value 最多可以容纳的数据长度是 512M 1.2、…

聊天、会议、多媒体一体化:多平台支持的即时通讯系统 | 开源日报 No.44

harness/gitness Stars: 28.2k License: Apache-2.0 Gitness 是一个建立在 Drone 之上的新型开源开发者平台&#xff0c;具备代码托管和流水线功能。它提供了以下核心优势&#xff1a; 轻量级、超快速的代码托管和持续集成服务支持 Docker 容器化部署可以在本地环境中构建和…

【C/C++笔试练习】二维数组、二维数组的访问,解引用,地址计算、计算糖果、进制转换

文章目录 C/C笔试练习1.二维数组&#xff08;1&#xff09;二维数组的访问&#xff08;2&#xff09;二维数组的初始化&#xff08;3&#xff09;二维数组的解引用&#xff08;4&#xff09;二维数组的解引用&#xff08;5&#xff09;多维数组的解引用&#xff08;6&#xff0…

没有社会性的人机环境系统智能是危险的

缺乏社会性的人工智能常常存在着一定的潜在危险性&#xff0c;这是因为&#xff1a; 首先&#xff0c;社会性对于人类而言是非常重要的&#xff0c;我们通过社交互动、合作和沟通来建立联系、理解他人以及共同解决问题。人类具有复杂的情感和道德价值观&#xff0c;这些因素在我…

嵌入式学习笔记(39)蜂鸣器和PWM定时器编程实践

7.4.1蜂鸣器的工作原理 (1)蜂鸣器里边有2个金属片&#xff0c;离得很近但没挨着。没电的时候两个金属片在弹簧本身的张力作用下分开彼此平行&#xff0c;有电的时候两边分别充电&#xff0c;在异性电荷的吸力作用下两个片挨着。 (2)我们只要以快速的频率给蜂鸣器的正负极供电…

redis使用学习笔记

文章目录 关于redis的简单性能概括Redis命令行客户端Redis命令Redis通用命令String类型key的层级格式Hash类型List类型Set类型SortedSet类型 Redis的Java客户端Jedis使用基本步骤Jedis连接池 SpringDataRedisRedisTemplate快速入门RedisSerializer 关于redis的简单性能概括 键…

c语言练习72:关于截断和整形提升

关于截断和整形提升 思考: 什么时候会发生截断和整形提升? 当以int的形式定义一个变量然后以char的形式输出是就会发生截断和整形提升 例如: #include<stdio.h> int main() {char c1, c2, c3;int i, j;c1 a, c2 97, c3 243;i b, j 1 c1;printf("%c %d\n…