zoukankan      html  css  js  c++  java
  • 写一个DCL懒汉单例式

    DCL:Double Check Lock 双重检查锁定

    首先一开始我们写一个懒汉单例式如下

    package com.interview.bintest.singleton;
    
    /**
     * 下面是DCL单例懒汉式的demo
     */
    public class DCLSingleton {
    
        /**
         * 因为是单例式,所以构造方法肯定是私有化
         * 无法通过new的方式来创建对象
         */
        private DCLSingleton(){
    
        }
    
        /**
         * 创建一个私有化静态成员变量,用于指向创建出来的单例
         */
        private static DCLSingleton dclSingleton;
    
        /**
         * 创建一个  公共静态  方法,提供给外部类调用
         * 方法则返回单例式(方法内创建)
         */
        public static DCLSingleton getInstance(){
            //因为是懒汉式创建,所以我们需要判断一下是否已经被创建了
            //没有则创建一个,有则直接返回该实例
            if(dclSingleton==null){//A
                dclSingleton = new DCLSingleton();//B
            }
            return dclSingleton;
        }
    
    
    
    }

    但是上面的代码在遇到多线程的时候就会产生问题,当x线程到达注释A处,判断完毕,条件成立,此时JVM把cpu的资源切换给y线程。

    y线程同样到达A处,因为x线程并没有创建实例,所以y执行了注释B处的代码,即完成了单例的创建。之后线程x被重新唤醒。

    因为x线程已经判断完了if中的条件,并且成立,于是x线程也执行了注释B处的代码,又创建了一个单例。这样就产生了线程不安全的问题。

    于是我们通过synchronized同步代码块来解决这个问题,代码如下

    package com.interview.bintest.singleton;
    
    /**
     * 下面是DCL单例懒汉式的demo
     */
    public class DCLSingleton {
    
        /**
         * 因为是单例式,所以构造方法肯定是私有化
         * 无法通过new的方式来创建对象
         */
        private DCLSingleton(){
    
        }
    
        /**
         * 创建一个私有化静态成员变量,用于指向创建出来的单例
         */
        private static DCLSingleton dclSingleton;
    
        /**
         * 创建一个  公共静态  方法,提供给外部类调用
         * 方法则返回单例式(方法内创建)
         */
        public static DCLSingleton getInstance(){
            //因为是懒汉式创建,所以我们需要判断一下是否已经被创建了
            //没有则创建一个,有则直接返回该实例
            synchronized (DCLSingleton.class){
                if(dclSingleton==null){//A
                    dclSingleton = new DCLSingleton();//B
                }
            }
            return dclSingleton;
        }
    
    
    
    }

    此时解决了上面的问题。但是新问题来了,因为synchronized的存在,每个线程在执行注释A的判断之前都会争抢锁,并且每个线程都要锁住了才能判断是否有实例存在。这样就导致了阻塞,因为同一时间下只能有一个线程执行synchronized里的语句,其余的线程都阻塞住。

    但是我们不能将注释A出的if条件判断提到外面将synchronized代码块包裹住。问题还是一样的,假设俩个线程都通过了判断,其中一个线程先获得锁进行了创建,后一个线程因为过了判断,所以获得前一个线程释放的锁,又进行一次创建。

    为了解决以上的问题,我们就需要进行两次判断,即双重检查锁定。代码如下

    package com.interview.bintest.singleton;
    
    /**
     * 下面是DCL单例懒汉式的demo
     */
    public class DCLSingleton {
    
        /**
         * 因为是单例式,所以构造方法肯定是私有化
         * 无法通过new的方式来创建对象
         */
        private DCLSingleton(){
    
        }
    
        /**
         * 创建一个私有化静态成员变量,用于指向创建出来的单例
         */
        private static DCLSingleton dclSingleton;
    
        /**
         * 创建一个  公共静态  方法,提供给外部类调用
         * 方法则返回单例式(方法内创建)
         */
        public static DCLSingleton getInstance(){
            //因为是懒汉式创建,所以我们需要判断一下是否已经被创建了
            //没有则创建一个,有则直接返回该实例
            /*
            情况一:实例还未创建
                假设x线程通过了第一个条件判断,此时x线程被挂起,y线程也通过了第一个判断
                并且执行了同步代码块,创建了一个实例,释放锁
                之后x线程被唤醒,执行同步代码块,再一次判断是否有实例存在,因为y线程创建了
                所以x线程不创建,释放锁。
            情况二:实例已经创建
                所有线程都无需执行同步代码块,在第一次条件判断不成立后直接返回实例即可
             */
            if (dclSingleton==null){
                synchronized (DCLSingleton.class){
                    if(dclSingleton==null){//A
                        dclSingleton = new DCLSingleton();//B
                    }
                }
            }
            return dclSingleton;
        }
    
    
    
    }

    但是现在仍然有一个问题,那就是在执行注释B处的代码时,该行代码是非原子性操作(即运行中途可能会被切换到另一个线程)。

    这行代码在底层被编译成了8条汇编指令。大致做了以下三件事

    (1)给创建的实例分配内存

    (2)调用实例构造器,完成初始化

    (3)将实例对象指向分配的内存空间

    因为JVM底层会优化指令,对指令进行重排序,以上的执行顺序就有可能变成:(1)(3)(2)

    当按照(1)(3)(2)的顺序执行到(3)时,此时这个线程被挂起

    另一个线程开始执行判断,这时候判断实例对象就不为空了,因为指向了一个地址。所以直接返回该实例对象,就会报错:对象尚未初始化。

    所以为了解决这个问题:需要禁止指令重排。(我也考虑过怎么让这一个部分变成原子操作,即不允许被打断,不过线程调度是系统的事,好像不容易改变)

    因此我们需要用到volatile关键字,它有个最重要的作用就是禁止指令重排,因此最终的代码如下

    package com.interview.bintest.singleton;
    
    /**
     * 下面是DCL单例懒汉式的demo
     */
    public class DCLSingleton {
    
    
        private DCLSingleton(){
    
        }
    
        private static volatile DCLSingleton dclSingleton;
    
        public static DCLSingleton getInstance(){
            if (dclSingleton==null){
                synchronized (DCLSingleton.class){
                    if(dclSingleton==null){//A
                        dclSingleton = new DCLSingleton();//B
                    }
                }
            }
            return dclSingleton;
        }
    
    
    
    }
  • 相关阅读:
    浏览器的垃圾回收机制
    vue-router传参数的方式
    Vue插槽
    自定义事件
    vue计算属性和监听器
    vue绑定样式
    循环中使用同步请求
    小白之路 | 从小学一年级期末考试看servlet+jsp技术
    Java实现简单计算器的探索性做法
    分布式数据库NoSQL简介
  • 原文地址:https://www.cnblogs.com/skyvalley/p/14244447.html
Copyright © 2011-2022 走看看