• java架构之路(多线程)大厂方式手写单例模式


    上期回顾:

      上次博客我们说了我们的volatile关键字,我们知道volatile可以保证我们变量被修改马上刷回主存,并且可以有效的防止指令重排序,思想就是加了我们的内存屏障,再后面的多线程博客里还有说到很多的屏障问题。

       volatile虽然好用,但是别用的太多,咱们就这样想啊,一个被volatile修饰的变量持续性的在修改,每次修改都要及时的刷回主内存,我们讲JMM时,我们的CPU和主内存之间是通过总线来连接的,也就是说,每次我们的volatile变量改变了以后都需要经过总线,“道路就那么宽,持续性的通车”,一定会造成堵车的,也就是我们的说的总线风暴。所以使用volatile还是需要注意的。

    单例模式:

      属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例),就是说每次我们创建的对象成功以后,在一个线程中有且仅有一个对象在正常使用。可以分为懒汉式和饿汉式。

      懒汉式就是什么意思呢,创建时并没有实例化对象,而是调用时才会被实例化。我们来看一下简单的代码。

    public class LasySingletonMode {
        public static void main(String[] args) {
            LasySingleton instnace = LasySingleton.getInstnace();
        }
    }
    
    class LasySingleton {
        /**
         * 私有化构造方法,禁止外部直接new对象
         */
        private LasySingleton() {
        }
    
        /**
         * 给予一个对象作为返回值使用
         */
        private static LasySingleton instnace;
    
        /**
         * 给予一个获取对象的入口
         *
         * @return LasySingleton对象
         */
        public static LasySingleton getInstnace() {
            if (null == instnace) {
                instnace = new LasySingleton();
            }
            return instnace;
        }
    }

      看起来很简单的样子,私有化构造方法,给予入口,返回对象,差不多就这样就可以了,但是有一个问题,如果是多线程呢?

    public static LasySingleton getInstnace() {
      if (null == instnace) {
        instnace = new LasySingleton();
      }
      return instnace;
    }

      我们假想两个线程,要一起运行这段代码,线程A进来了,看到instnace是null的,ε=(´ο`*)))唉,线程B进来看见instnace也是null的(因为线程A还没有运行到instnace = new LasySingleton()这个代码),这时就会造成线程A,B创建了两个对象出来,也就不符合我们的单例模式了,我们来改一下代码。

    public static LasySingleton getInstnace() {
        if (null == instnace) {
            synchronized (LasySingleton.class){
                instnace = new LasySingleton();
            }
        }
        return instnace;
    }

      这样貌似就可以了,就算是两个线程进来,也只有一个对象可以拿到synchronized锁,就不会产生new 两个对象的行为了,其实不然啊,我们还是两个线程来访问我们的这段代码,线程A和线程B,两个线程来了一看,对象是null的,需要创建啊,于是线程A拿到锁,开始创建,线程B继续等待,线程A创建完成,返回对象,将锁释放,这时线程B可以获取到锁(因为null == instnace判断已经通过了,在if里面进行的线程等待),这时线程B还是会创建一个对象的,这显然还是不符合我们的单例模式啊,我们来继续改造。

    public static LasySingleton getInstnace() {
        if (null == instnace) {
            synchronized (LasySingleton.class){
                if (null == instnace) {
                    instnace = new LasySingleton();
                }
            }
        }
        return instnace;
    }

      这次基本就可以了吧,回想一下我们上次的volatile有序性,难道真的这样就可以了吗?instnace = new LasySingleton()是一个原子操作吗?有时候你面试小厂,这样真的就可以了,我们来继续深挖一下代码。看一下程序的汇编指令码,首先找我们的class文件。运行javap -c ****.class。

    E:IdeaProjects	uling-mvc-3	argetclassescom	ulingcontrol>javap -c LasySingleton.class
    Compiled from "LasySingletonMode.java"
    class com.tuling.control.LasySingleton {
      public static com.tuling.control.LasySingleton getInstnace();
        Code:
           0: aconst_null
           1: getstatic     #2                  // Field instnace:Lcom/tuling/control/LasySingleton;
           4: if_acmpne     17
           7: new           #3                  // class com/tuling/control/LasySingleton
          10: dup
          11: invokespecial #4                  // Method "<init>":()V
          14: putstatic     #2                  // Field instnace:Lcom/tuling/control/LasySingleton;
          17: getstatic     #2                  // Field instnace:Lcom/tuling/control/LasySingleton;
          20: areturn
    }

       不是很好理解啊,我们只想看instnace = new LasySingleton()是不是一个原子操作,我们可以这样来做,创建一个最简单的类。

    public class Demo {
        public static void main(String[] args) {
            Demo demo = new Demo();
        }
    }

    然后我们运行javap -c -v ***.class

    E:IdeaProjects	uling-mvc-3	argetclasses>javap -c -v Demo.class
    Classfile /E:/IdeaProjects/tuling-mvc-3/target/classes/Demo.class
      Last modified 2020-1-13; size 389 bytes
      MD5 checksum f8b222a4559c4bf7ea05ef086bd3198c
      Compiled from "Demo.java"
    public class Demo
      minor version: 0
      major version: 49
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
       #2 = Class              #20            // Demo
       #3 = Methodref          #2.#19         // Demo."<init>":()V
       #4 = Class              #21            // java/lang/Object
       #5 = Utf8               <init>
       #6 = Utf8               ()V
       #7 = Utf8               Code
       #8 = Utf8               LineNumberTable
       #9 = Utf8               LocalVariableTable
      #10 = Utf8               this
      #11 = Utf8               LDemo;
      #12 = Utf8               main
      #13 = Utf8               ([Ljava/lang/String;)V
      #14 = Utf8               args
      #15 = Utf8               [Ljava/lang/String;
      #16 = Utf8               demo
      #17 = Utf8               SourceFile
      #18 = Utf8               Demo.java
      #19 = NameAndType        #5:#6          // "<init>":()V
      #20 = Utf8               Demo
      #21 = Utf8               java/lang/Object
    {
      public Demo();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 1: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   LDemo;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #2                  // class Demo
             3: dup
             4: invokespecial #3                  // Method "<init>":()V
             7: astore_1
             8: return
          LineNumberTable:
            line 3: 0
            line 4: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  args   [Ljava/lang/String;
                8       1     1  demo   LDemo;
    }
    SourceFile: "Demo.java"
    
    E:IdeaProjects	uling-mvc-3	argetclasses>

    结果是这样的,我们来分析一下代码,先看这个

     0: new           #2                  // class Demo

    就是什么意思呢?我们要给予Demo对象在对空间上开辟一个空间,并且返回内存地址,指向我们的操作数栈的Demo对象

    3: dup

    是一个对象复制的过程。

     4: invokespecial #3                  // Method "<init>":()V

    见名知意,init是一个初始化过程,我们会把我们的刚才开辟的栈空间进行一个初始化,

    7: astore_1

      这个就是一个赋值的过程,刚才我们有个复制的操作对吧,这时会把我们复制的一个对象赋值给我们的栈空间上的Demo,是不是有点蒙圈了,别急,后面的简单。

      这是一个对象的初始化过程,在我的JVM系列博客简单的说过一点,后面我会详细的去说这个,总结起来就是三个过程。

    1.开辟空间
    2.初始化空间
    3.给引用赋值

      这个代码一般情况下,会按照123的顺序去执行的,但是超高并发的场景下,可能会变为132,考虑一下是不是,我们的as-if-serial,132的执行顺序在单线程的场景下也是合理的,如果真的出现了132的情况,会造成什么后果呢?回到我们的单例模式,所以说我们上面单例模式代码还需要改。

    public class LasySingletonMode {
        public static void main(String[] args) {
            LasySingleton instnace = LasySingleton.getInstnace();
        }
    }
    
    class LasySingleton {
    
    
        /**
         * 私有化构造方法,禁止外部直接new对象
         */
        private LasySingleton() {
        }
    
        /**
         * 给予一个对象作为返回值使用
         */
        private static volatile LasySingleton instnace;
    
        /**
         * 给予一个获取对象的入口
         *
         * @return LasySingleton对象
         */
        public static LasySingleton getInstnace() {
            if (null == instnace) {
                synchronized (LasySingleton.class) {
                    if (null == instnace) {
                        instnace = new LasySingleton();
                    }
                }
            }
            return instnace;
        }
    }

      这样来写,就是一个满分的单例模式了,无论出于什么样的考虑,都是满足条件的。也说明你真的理解了我们的volatile关键字。

      饿汉式相当于懒汉式就简单很多了,不需要考虑那么多了。

    package com.tuling.control;
    
    public class HungrySingletonMode {
        public static void main(String[] args) {
            String name = HungrySingleton.name;
            System.out.println(name);
        }
    }
    
    class HungrySingleton {
    
        /**
         * 私有化构造方法,禁止外部直接new对象
         */
        private HungrySingleton() {
        }
    
        private static HungrySingleton instnace =  new  HungrySingleton();
    
        public static String name = "XXX";
    
        static{
            System.out.println("我被创建了");
        }
        
        public static HungrySingleton getInstance(){
            return instnace;
        }
    }

      很简单,也不是属于我们多线程范畴该说的,这里就是带着说了一下,就是当我们调用内部方法时,会主动触发对象的创建,这样就是饿汉模式。

  • 相关阅读:
    js面向对象总结
    css3重点回顾字体
    URI和URL的区别
    nodejs 利用zip-local模块压缩文件夹
    vue cli3 vue.config.js 配置详情
    如何在TypeScript中使用第三方JavaScript框架
    代码简洁之道
    js判断一个图片是否已经存在于缓存
    png8、16、24、32位的区别
    实现前端路由
  • 原文地址:https://www.cnblogs.com/cxiaocai/p/12189488.html
Copyright © 2020-2023  润新知