05 - 单例模式

定义: 某一种对象,全局只能创建一个。

单例模式犹如茴香豆有N中写法。

  1. 构造函数设置成私有
  2. 静态构造方法构造对象。
public class Singleton {
  private static final Singleton s = new Singleton();
  private Singleton() {
  }
  public static Singleton getInstance() {
        return s;
   }
}

按照java的语法,都知道静态变量会自动初始化,并且final的变量只会初始化一次并且引用不可改变,这样我们只有一个Singleton对象的引用。

但是这个Singleton对象什么时候创建出来的呢,什么时候执行的呢?

反编译一下:

Classfile /root/java/Singleton.class
  Last modified Jun 4, 2018; size 369 bytes
  MD5 checksum 1820528d8fe309e7c6bff10a17dd9113
  Compiled from "Singleton.java"
public class Singleton
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."":()V
   #2 = Fieldref           #3.#18         // Singleton.s:LSingleton;
   #3 = Class              #19            // Singleton
   #4 = Methodref          #3.#17         // Singleton."":()V
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               s
   #7 = Utf8               LSingleton;
   #8 = Utf8               
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               getInstance
  #13 = Utf8               ()LSingleton;
  #14 = Utf8               
  #15 = Utf8               SourceFile
  #16 = Utf8               Singleton.java
  #17 = NameAndType        #8:#9          // "":()V
  #18 = NameAndType        #6:#7          // s:LSingleton;
  #19 = Utf8               Singleton
  #20 = Utf8               java/lang/Object
{
  public static Singleton getInstance();
    descriptor: ()LSingleton;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field s:LSingleton;
         3: areturn
      LineNumberTable:
        line 6: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #3                  // class Singleton
         3: dup
         4: invokespecial #4                  // Method "":()V
         7: putstatic     #2                  // Field s:LSingleton;
        10: return
      LineNumberTable:
        line 2: 0
}
SourceFile: "Singleton.java"

可以看到,s的初始化放到了static()方法里,也就是类构造器(cinit);

public class Singleton {
  private static final Singleton s 
 static { 
    s = new Singleton();
  }
  private Singleton() {
  }
  public static Singleton getInstance() {
        return s;
   }
}

这个方法什么时候执行呢?
类初始化的时候

类的生命周期是:
加载,验证,准备,解析,初始化,使用,卸载。

当使用的时候,一般都是 Singleton a = Singleton.getInstance(); 此时遇到了指令invokestatic ,类必须要初始化,Singleton类 进行加载,验证,准备(清零),解析(接口,字段解析),初始化。

jvm在执行cinit方法时,会采取加锁和同步的方式。避免其他线程也执行,并且在本线程执行完毕后,其他线程也无法执行。

因此该对象的创建是线程安全的。

大多数情况下,这样设计就可以的。


进一步要考虑的是懒加载(调用的时候再创建,不要再加载的时候创建),线程安全,以及序列化安全。

如果创建该对象的时间很长,可以采用一些办法,在使用的时候再创建。
内部类懒加载

public class Single {
  private Single() {
 }
  private static class Holder {
      private final static Single s = new Single();
  }
  public static Single getInstance() {
     return Holder.s;
  }
}

这样Single类如果因为其他情况,发生了加载,也不会初始化s。
只有调用getInstance() 时,加载Single类,执行Holder.s ,对应指令是invokestatic,加载Holder类,初始化s,并返回。这个过程是jvm控制加锁,线程安全。


序列化安全使用枚举即可,避免反射和序列化的攻击。
不过jdk打算把序列化从jdk中移除出去,JDK认为有至少三分之一的安全问题都是Serializable引起的。

END

你可能感兴趣的:(05 - 单例模式)