单例模式及其线程安全问题

这篇具有很好参考价值的文章主要介绍了单例模式及其线程安全问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单例模式及其线程安全问题

目录

1.设计模式

2.饿汉模式

3.懒汉模式

4.线程安全与单例模式


1.设计模式

设计模式是什么?

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案

这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的

单例模式的作用就是保证某个类在程序中只存在唯一一份实例,不会创建出多个实例(之前学过的JDBC编程,DataSource这样的类就适合单例模式)

特点:

  • 1、单例类只能有一个实例
  • 2、单例类必须自己创建自己的唯一实例
  • 3、单例类必须给所有其他对象提供这一实例

单例模式分为"饿汉""懒汉"两种

2.饿汉模式

//饿汉模式
//此处保证只能创建一个实例
class Singleton{
    private static Singleton instance = new Singleton();
    //想要使用时,通过Singleton.getInstance()来获取!
    public static Singleton getInstance(){
        return instance;
    }
    //构造方法私有化,类外无法通过new来调用构造器创建实例!!
    private Singleton(){}
}
public class Thread {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1==singleton2);
    }
}

构造器私有化之后是不能通过new来调用构造器实例化对象的

单例模式及其线程安全问题

此处我们将这个实例设置成私有的,通过get方法来获取,并且将构造方法私有化,不能创建新实例,因此访问这个实例的时候,每次访问得到的是同一个引用 

单例模式及其线程安全问题

private static Singleton instance = new Singleton();

Singleton这个属性和实例无关,是和类相关的,java代码中的每个类在编译完成后都会得到.class文件,JVM运行时会加载这个文件读取其中的二进制指令,并在内存中构造对应的类对象(Singleton.class),这个过程就是类加载的过程

该模式是如何保证实例唯一呢

1.static修饰的实例instance,让当前实例的属性是类属性.在类加载阶段就被创建,一个类只加载一次,这个实例只创建唯一一份

2.构造方法私有化,类外无法再创建新的实例 

这个单例模式的名称是"饿汉模式",这个名字的来由是与后面的"懒汉模式"相比较得出的,体现在:在类加载阶段,就直接创建出了实例,实在很靠前的阶段给人一种急迫的感觉,所以叫饿汉模式

3.懒汉模式

//懒汉模式
class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){};
}
public class Thread {
    public static void main(String[] args) {
        SingletonLazy singleton3 = SingletonLazy.getInstance();
        SingletonLazy singleton4 = SingletonLazy.getInstance();
        System.out.println(singleton3==singleton4);
    }
}

 单例模式及其线程安全问题

懒汉模式的实例初始情况下是null,并非是在类加载时就创建出来了,而是第一次使用的时候才创建出来的,如果没有使用,那么就不创建了,单从效率来说是更好的选择

4.线程安全与单例模式

上述两种模式在多线程环境下调用getInstance是否是线程安全的呢?

先分析一下饿汉模式

单例模式及其线程安全问题 在饿汉模式中.多线程调用只涉及到了"读" 操作,我们知道多个线程只读一个变量是安全的,那么这个饿汉模式就是安全的

再看懒汉模式

单例模式及其线程安全问题

 这里涉及到了"读和写"两个操作,在多线程中调用,是不安全的

单例模式及其线程安全问题

上述途中两个线程调用时,由于随机调度和指令重排序的特点,如果在t1线程还没有创建出实例,t2线程就调用,那么instance还是null,继续往下执行,那么t1t2会创建出两个实例,触发多次new操作了,就不满足单例模式这个应用场景的需求了!!导致了线程不安全

如何解决这个线程安全问题呢?

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

加锁肯定是解决线程安全问题的普适方法

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

这种加锁方式是不可取的!!这里只给new操作加锁了,那么t2还是可能读到t1没来得及写的数据,所以我们要给整个操作加锁! 保证读,比较,new,写这几个操作整体是原子的,正确的加锁方法如下

单例模式及其线程安全问题

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

到这里t2读到的就是t1更新过的数据了,是一个非空值,不会触发if条件,也就不能new新的实例了,满足了单例模式的要求

但是我们每个线程调用get时都要加锁,加锁操作也是有开销的,频繁的加锁会降低效率.我们发现一旦有一个实例后,后续调用get时,instance肯定是非空的,就直接触发return,那么就不需要锁了!

所以我们再进行一个判定,如果对象还没创建就加锁,创建过了,就不加锁!

这种方式采用双校验锁机制,安全且在多线程情况下能保持高性能

getInstance() 的性能对应用程序很关键时使用

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

单例模式及其线程安全问题

1处的if语句是判定是否需要加锁!

2处的if语句是判定是否要创建实例对象!

这两个连续相同的if语句在没有加锁的情况下是没有意义的,一个两个效果相同,但是中间加了锁,就可能引起线程阻塞,等到解锁之后,第一个if和第二个if之间对于计算机来说已经沧海桑田了!程序内部的状态,变量的值都可能发生很大改变

这样减少了不必要的加锁,但是还存在内存可见性问题!

假设有很多线程都来调用get,这个时候第一次调用是读内存,后续都是读寄存器/cache,那么就会有被优化的风险!

还有指令重排序引入的线程安全问题,new操作可以拆分为三个步骤

1.申请内存空间

2.调用构造方法,初始化对象

3.把空间地址赋给instance引用

编译器可能会为了提高程序效率将指令执行顺序调整,1不会被调整.23会被调整,单线程情况写123,132没有本质区别,最后都能new出实例对象,但是多线程情况下,t1如果执行132,执行到13后就被切换到t2来执行,此时t1的2还没有执行,instance仍然是一个null,t2却认为t1已经执行完3了,那么此处的引用就是非null的了,按照代码t2会直接返回一个instance引用,可能还会尝试使用引用中的属性,但是这是一个非法的实例对象,它并没有被构造完成!

解决内存可见性,指令重排序问题需要用到关键字--volatile

所以要使用volatile修饰instance!!这样就能解决内存可见性和指令重排序

单例模式及其线程安全问题

 线程安全的饿汉版单例模式:文章来源地址https://www.toymoban.com/news/detail-434824.html

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

到了这里,关于单例模式及其线程安全问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [Linux] 最基础简单的线程池 及其 单例模式的实现

    本篇文章主要用到线程相关内容, 下面是博主关于线程相关内容的文章: [Linux] 线程同步分析:什么是条件变量?生产者消费者模型是什么?POSIX信号量怎么用?阻塞队列和环形队列模拟生产者消费者模型 [Linux] 线程互斥分析: 多线程的问题、互斥锁、C++封装使用互斥锁、线程安

    2024年02月16日
    浏览(38)
  • 【多线程】线程安全的单例模式

    单例模式能保证某个类在程序中只存在 唯一 一份实例, 而不会创建出多个实例,从而节约了资源并实现数据共享。 比如 JDBC 中的 DataSource 实例就只需要一个. 单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种. 类加载的同时, 创建实例. 注意: 使用 static 修饰 instanc

    2024年02月09日
    浏览(46)
  • 线程安全之单例模式

    这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把

    2024年02月06日
    浏览(62)
  • 单例模式的线程安全形式

    目录 1.单例设计模式的概念 2.实现方法: 1.饿汉式 2.懒汉式 3.区分饿汉式和懒汉式: 3.单例模式的双重校验线程安全形式 1.线程安全问题的解决方法 1.1 synchronized: 1.2 volatile:         保证变量可见性(不保证原子性)         禁止指令的重排序 2.线程安全

    2024年02月15日
    浏览(54)
  • 设计模式3:单例模式:静态内部类模式是怎么保证单例且线程安全的?

    上篇文章:设计模式3:单例模式:静态内部类单例模式简单测试了静态内部类单例模式,确实只生成了一个实例。我们继续深入理解。 静态变量什么时候被初始化? 这行代码 private static Manager instance = new Manager(); 什么时候执行? 编译期间将.java文件转为.class文件,运行期间

    2024年02月12日
    浏览(46)
  • 【并发专题】单例模式的线程安全(进阶理解篇)

    最近学习了JVM之后,总感觉知识掌握不够深,所以想通过分析经典的【懒汉式单例】来加深一下理解。(主要是【静态内部类】实现单例的方式)。 如果小白想理解单例的话,也能看我这篇文章。我也通过了【前置知识】跟【普通懒汉式】、【双检锁懒汉】、【静态内部类】

    2024年02月14日
    浏览(42)
  • Java 枚举实现单例模式,线程安全又优雅!

    这种DCL写法的优点:不仅线程安全,而且延迟加载。 1.1 为什么要double check?去掉第二次check行不行? 当然不行,当2个线程同时执行getInstance方法时,都会执行第一个if判断,由于锁机制的存在,会有一个线程先进入同步语句,而另一个线程等待,当第一个线程执行了 new Sin

    2024年02月02日
    浏览(40)
  • 【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式

    🍎 个人博客: 个人主页 🏆 个人专栏: JAVA ⛳️   功不唐捐,玉汝于成 目录 前言 正文 懒汉式(Lazy Initialization): 双重检查锁定(Double-Checked Locking): 结语 我的其他博客 在软件设计中,单例设计模式是一种重要的设计思想,它确保了一个类只有一个实例,并提供了一

    2024年01月15日
    浏览(55)
  • 【Java中23种设计模式-单例模式2--懒汉式2线程安全】

    加油,新时代打工人! 简单粗暴,学习Java设计模式。 23种设计模式定义介绍 Java中23种设计模式-单例模式 Java中23种设计模式-单例模式2–懒汉式线程不安全 通过运行结果看,两个线程的地址值是相同的,说明内存空间里,创建了一个对象。

    2024年02月20日
    浏览(46)
  • linux线程池、基于线程池的单例模式、读者写者问题

    线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

    2024年02月03日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包