• 懒汉式、饿汉式


    懒汉式

    懒汉式:刚开始不初始化,在用的时候再进行初始化。

    懒汉单例双重检查真的安全吗?

    代码示例:

    /**
     * 懒汉式-双重检查-非线程安全
     */
    public class SingleDclNotsafe {
        
        private static SingleDclNotsafe singleDcl;
        //私有化
        private SingleDclNotsafe(){
        }
    
        public static SingleDclNotsafe getInstance(){
            if (singleDcl == null){ //第一次检查,不加锁
                System.out.println(Thread.currentThread()+" is null");
                synchronized(SingleDclNotsafe.class){ //加锁
                    if (singleDcl == null){ //第二次检查,加锁情况下
                        System.out.println(Thread.currentThread()+" is null");
                        singleDcl = new SingleDclNotsafe();
                    }
                }
            }
            return singleDcl;
        }
    }

    为什么是不安全的?

    首先,先清楚在 new SingleDclNotsafe();这一步操作上所做的三件事情:

      1. 内存中分配空间
      2. 空间初始化
      3. 把这个空间的地址给我们的引用

    问题就出现在这这里!!!

    有可能它先执行的1 > 3 > 2,其中在执行完 3 后,第 2 步 还没有完成,这时,这个对象已经不为空,然后直接就返回出去了。。。

    就会有一个不完整的对象,怎么说呢? 

    代码举例:

    public class SingleDclNotsafe {
    
        private int a;
        private String s;
        private static SingleDclNotsafe singleDcl;
        //私有化
        private SingleDclNotsafe(){
        }
    
        public static SingleDclNotsafe getInstance(){
            if (singleDcl == null){ //第一次检查,不加锁
                System.out.println(Thread.currentThread()+" is null");
                synchronized(SingleDclNotsafe.class){ //加锁
                    if (singleDcl == null){ //第二次检查,加锁情况下
                        System.out.println(Thread.currentThread()+" is null");
                        //内存中分配空间  1
                        //空间初始化 2
                        //把这个空间的地址给我们的引用  3
                        singleDcl = new SingleDclNotsafe();
                    }
                }
            }
            return singleDcl;
        }
    }

    在空间地址给这个引用时,可能还未完成空间的初始化,这个时候可能 int a 已经初始化完成了,但是String s 还未初始化就被返回了。这样的话在外部用这个对象 get 属性时:getA()没有问题,getS()就会NullPointException,因为 String s 还未初始化完就已经被返回了。

    所以说,它是线程不安全的。

    怎么样做到懒汉单例线程安全?

    解决方法一:加 volatile 关键字。

    通过使用包含多个状态变量的容器对象来维持不变性条件,并且使用一个 volatile 类型的引用来确保可见性,从而保证了线程安全。

       volatile:1.可见性;2.避免重排序,JSR屏障

    JSR内存屏障:JSR是JavaSpecification Requests的缩写,意思是“Java 规范提案”

    LoadLoad:对于这样的语句Load1;LoadLoad;Load2,在Load2及后续的读操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕;

    StoreStore:对于这样的语句Store1;StoreStore;Store2,在Store2及后续的写操作执行前,保证Store1的写入操作对其他处理器可见;

    LoadStore:对于这样的语句Load1;LoadStore;Store2,在Store2及后续的写入操作被刷出前,保证Load1要读取的数据被读取完毕;

    StoreLoad:对于这样的语句Store1;StoreLoad;Load2,在Load2及后续的读操作要读取的数据被访问前,保证Store1的写入操作对其他处理器可见;

      as-if-serial:不管如何重排序,单线程执行结果不会改变。在JVM层面volatile的实现细节特别保守,保证了内存可见性,并且成功防止指令重排序。

    代码示例:

    /**
     * 懒汉式-双重检查(DCL:double check lock)--线程安全
     */
    public class SingleDclSafe {
       
       // volatile:1.可见性;2.避免重排序,JSR屏障
    private volatile static SingleDclSafe singleDcl; //私有化 private SingleDclSafe() { } public static SingleDclSafe getInstance() { if (singleDcl == null) { //第一次检查,不加锁 System.out.println(Thread.currentThread() + " is null"); synchronized (SingleDclSafe.class) { //加锁 if (singleDcl == null) { //第二次检查,加锁情况下 System.out.println(Thread.currentThread() + " is null"); singleDcl = new SingleDclSafe(); } } } return singleDcl; } }

    解决方法二:延迟初始化占位类模式

    原理:

    运用虚拟机的类加载保证线程安全,虚拟机在实现的时候考虑到:这个类在加载的时候 ,可能跟会有多个线程同时加载这个类,但是在虚拟机里面,某个类只能被加载一次(class对象只有一个)。所以当多个线程同时加载这个类时,虚拟机就会进行加锁,保证只有一个线程完成这个所谓的类加载。

    /**
     * 懒汉式-延迟初始化占位类模式--线程安全
     */
    public class SingleInit {
        private SingleInit() {
        }
    
        private static class InstanceHolder {
            private static SingleInit instance = new SingleInit();
        }
    
        public static SingleInit getInstance() {
            return InstanceHolder.instance;
        }
    
    }

    饿汉式

    饿汉式:在开始就初始化完成了。

    饿汉式是线程安全的。

    原理:同上面的虚拟机加载原理一样。

    代码示例

    /**
     * 饿汉式--线程安全
     */
    public class SingleEHan {
        private SingleEHan() {
        }
    
        private static SingleEHan singleDcl = new SingleEHan();
    
    }
  • 相关阅读:
    对获取的DataTable表进行过滤筛选得到DataView
    简单提取iOS13的ipsw固件的内置壁纸(或文件)
    win10设置Python程序定时运行(设置计划任务)
    后端返回一个这种类型的时间格式给前端2020-01-16T09:10:02.349Z
    js把每个词的首字母转大写
    idea连接mysql自动生成实体类
    el自定义函数
    js日期时间格式化
    js大小写转换
    js瞄点
  • 原文地址:https://www.cnblogs.com/mjtabu/p/12871543.html
Copyright © 2020-2023  润新知