zoukankan      html  css  js  c++  java
  • 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;
        }
    }

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

  • 相关阅读:
    Server Tomcat v8.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
    用户画像——“打标签”
    python replace函数替换无效问题
    python向mysql插入数据一直报TypeError: must be real number,not str
    《亿级用户下的新浪微博平台架构》读后感
    【2-10】标准 2 维表问题
    【2-8】集合划分问题(给定要分成几个集合)
    【2-7】集合划分问题
    【2-6】排列的字典序问题
    【2-5】有重复元素的排列问题
  • 原文地址:https://www.cnblogs.com/cxiaocai/p/12189488.html
Copyright © 2011-2022 走看看