定义: 某一种对象,全局只能创建一个。
单例模式犹如茴香豆有N中写法。
- 构造函数设置成私有
- 静态构造方法构造对象。
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."<init>":()V
#2 = Fieldref #3.#18 // Singleton.s:LSingleton;
#3 = Class #19 // Singleton
#4 = Methodref #3.#17 // Singleton."<init>":()V
#5 = Class #20 // java/lang/Object
#6 = Utf8 s
#7 = Utf8 LSingleton;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 getInstance
#13 = Utf8 ()LSingleton;
#14 = Utf8 <clinit>
#15 = Utf8 SourceFile
#16 = Utf8 Singleton.java
#17 = NameAndType #8:#9 // "<init>":()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 "<init>":()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引起的。