单例模式的进一步实现方式

首先我们需要了解:
Java内存模型的三大特性 :
(1)原子性 : 由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和read。大致可以认为,基本数据类型的访问读写是具备原子性的。如若需要更大范围的原子性,需要synchronized关键字约束。(即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行)
(2)可见性 : 可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。volatile、synchronized、final三个关键字可以实现可见性。
(3)有序性 : 如果在本线程内观察,所有的操作都是有序的;如果在线程中观察另外一个线程,所有的操作都是无序的。前半句是指"线程内表现为串行",后半句是指"指令重排序"和"工作内存与主内存同步延迟"现象

单例模式:在前面我谈到了基本的懒汉式和饿汉式单例模式。但是它不具有安全性,不是很完善。
单例模式的进一步探讨:
(1)单例模式中的Double Check:
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。
那么为什么在同步块内还要再检验一次?
答:因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
代码实现:

class Singleton {
    private static Singleton instance = null;//声明对象

    public static Singleton getSingleton() {
        if (instance == null) { //第一次检验
            synchronized (Singleton.class) {
                if (instance == null) { //第二次检验
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    public void print(){
        System.out.println("ZH");
    }
}
public class SingleTonTest {
    public static void main(String[] args) {
      Singleton singleton=null;
      singleton=Singleton.getSingleton();
      singleton.print();
    }
}

但是instance = new Singleton(),不是一个原子的操作。
instance = new Singleton()包含了一下三个步骤:
1.开辟内存空间
2.初始化(调用构造方法)
3.赋值给变量
在这里插入图片描述
但是在 JVM 的即时编译器中存在指令重排序的优化。
即它可能的执行顺序为:
1.开辟内存空间
2.赋值给变量
3.初始化(调用构造方法)
则在赋值给变量 执行完毕、初始化未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后就错误。
那我们如何解决该问题呢?
将 instance 变量声明成volatile 就可以了。
此时我们需要知道:当一个变量定义为volatile之后,它将具备两种特性。
1.保证此变量对所有线程的可见性。
指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
2.使用volatile变量的语义是禁止指令重排序
普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果
的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序和程序代码中执行的顺序一致。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
代码实现:

class Singleton1 {
    private volatile static Singleton instance = null;

    private Singleton1() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            //检查对象是否初始化
            synchronized (Singleton.class) {
                if (instance == null) {//确保多线程情况下对象只有一个
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

常见问题:1.它可以获得多个实例对象吗?
答:可以,通过反射获得。
2.但它是私有的,为啥可以?
答:因为反射中的setAccessible()可以修改,可以改掉权限。
3.如何解决产生多个实例对象的问题呢?
答:通过枚举的方法,既解决了该问题,还很安全。

若有问题,望及时指出。

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值