美文网首页
面试题剖析:单例设计模式线程安全问题

面试题剖析:单例设计模式线程安全问题

作者: Petrel_Huang | 来源:发表于2020-04-27 01:12 被阅读0次

1. volatile 关键字

1.1 volatile 关键字作用:

在百度百科截取的描述如下:

image.png

说明volatile 关键字作用作用有两点:

  1. 防止指令重排:规定了volatile 变量不能指令重排,必须先写再读。

  2. 内存可见:线程从内存中读取volatile修饰的变量的数据,直接从主内存中获取数据,不需要经过CPU缓存,这样使得多线程获取的数据都是一致的。如图所示:


    image.png

1.2 volatile和synchronized的区别

volatile不能够替代synchronized,原因有两点:

1.对于多线程,不是一种互斥关系
2.不能保证变量状态的“原子性操作”,所以volatile不能保证原子性问题

1.3解决单例设计模式线程安全问题

实现单例设计模式两种

  1. 饿汉式(不存在原子性,是线程安全的)

实现1:

//饿汉式:很饿需要立马创建对象
public class Singleton1 {
    //1.定义一个对象
    private static final Singleton1 instance = new Singleton1();
    //2.私有化构造器,避免外部类创建对象
    private Singleton1(){}
    //3.获取对象的静态方法
    public static Singleton1 getInstance(){
        return instance;
    }
}

实现2:枚举方式(最安全)

//饿汉式(枚举)
public enum EnumSingleton {
   INSTANCE;
}
  1. 懒汉式(懒加载):存在原子性问题,线程不安全
//懒汉式:很懒,使用对象的时候才创建对象,但是省资源
public class Singleton2 {
    private static Singleton2 instance;

    private Singleton2() {
    }

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

①由于 instance = new Singleton2();存在原子性问题,所以我们应该用synchronized代码块将其同步。这里由于synchronized很耗资源,所以粒度越小越好,最好不要使用同步方法。

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

②在多个线程的情况,可能存在线程1和线程2都已经执行了instance == null的判断,可能线程1抢到了锁线程2就阻塞在了同步代码块入口,当线程1执行完毕释放锁,线程2拿到锁的时候因为之前判断instance == null为true就会创建对象,那么此时就无法保证单例了,所以我们应该继续在同步代码块中再判断一次instance == null。这样的做法我们有个专业名词,称之为双重检查锁定。

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

③instance = new Singleton2();这句代码存在指令重排问题,什么意思?

一般的执行顺序为:

1)给对象分配内存空间
2)初始化对象
3)变量instance 指向内存空间

在单线程中,由于步骤2)和步骤3)即使交换顺序也不会影响最终效果,所以可能发生指令重排,顺序为:

1)给对象分配内存空间
3)变量instance 指向内存空间
2)初始化对象

如果出现指令重排就会发生以下问题,如图所示:

image.png

注意:由于线程2在外面的判断就为false,没有去运行需要竞争锁的代码,所以没有进入阻塞状态,和线程1是并行状态,导致访问对象出现问题,所以为了避免这个问题,我们应该不让指令重排发生,那么使用volatile修饰对象,让对象先写再读,固定对象的指令,避免指令重排。

最终线程安全的单例懒汉式代码如下:

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

相关文章

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • Java16-3 单例设计模式下的多线程访问

    当一个单例设计模式被多线程并发访问时,也会出现安全问题。 懒汉式单例设计模式(推荐使用) 不会因为同步而出现安全问...

  • 面试题剖析:单例设计模式线程安全问题

    1. volatile 关键字 1.1 volatile 关键字作用: 在百度百科截取的描述如下: 说明volat...

  • 面试题剖析:单例设计模式线程安全问题

    本文作者:黄海燕,叩丁狼高级讲师。原创文章,转载请注明出处。 1. volatile 关键字 1.1 volati...

  • 设计模式——单例模式的破坏

    概述: 之前学习了单例模式的几种实现,解决了多线程情况下,单例的线程安全问题,保证了单例的实现。但是单例模式在下面...

  • Singleton 单例模式

    饿汉式单例模式 饿汉式单例模式 通过静态代码块增加异常处理 懒汉式单例模式 存在线程安全问题 懒汉式单例模式 解决...

  • 源码 : 设计模式之单例模式的5种实现

    RelaxHeart网-王琦:源码 : 设计模式之单例模式的5种实现 方式一:饿汉式 方式二:懒汉式 线程安全问题...

  • 设计模式

    手写单例模式(线程安全) 你知道几种设计模式?单例模式是什么?Spring中怎么实现单例模式?

  • 单例模式之双重检查的演变

    前言 单例模式本身是很简单的,但是考虑到线程安全问题,简单的问题就变复杂了。这里讲解单例模式的双重检查。 单例模式...

  • 多线程Debug窥探单例模式

    1. 懒汉式单例模式 通过延迟初始化,降低单例创建期间的资源开销。 懒汉式单例实现,存在线程安全问题 线程任务 在...

网友评论

      本文标题:面试题剖析:单例设计模式线程安全问题

      本文链接:https://www.haomeiwen.com/subject/yowkcqtx.html