饿汉模式
public class Singleton1 implements Serializable {
private static Singleton1 instance = new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return instance;
}
}
是怎么保证线程安全的?
线程安全的懒汉模式
双重检测锁(Double Checked Lock DCL) + volatitle
- DCL,避免多个线程同时指执行到if(instance == null),判断都为true,会排队创新等待创建新对象
- volatitle关键字 防止CPU指令重排,instance = new Singleton4()不是原子操作,会涉及三个步骤,CPU执行顺序有可能会乱(给实例分配内存空间;调用对象的构造方法,初始化成员字段;将instance 对象指向分配的内存空间)
public class Singleton4 {
private volatile static Singleton4 instance;
private Singleton4(){}
public static Singleton4 getInstance(){
if(instance == null){
synchronized (Singleton4.class){
if(instance == null){
instance = new Singleton4();
}
}
}
return instance;
}
}
在第一次调用Singleton4.getInstance()的时候初始化静态变量
类并不是在加载完之后就会实例static变量。那到底什么时候才会初始化呢?
1.当遇到new,getstatic,putstatic或者invokestatic这四条字节码指令的时候,如果该类没有进行
初始化,则需要先初始化.这四条指令对应的是实例化对象,获取一个静态变量,设置一个静态变量(常量
放在常量池中,不会触发),或者调用静态方法的时候.
2.当时候反射包的方法对类进行反射调用的时候
3.当初始化一个类的时候,发现该类的父类还没有进行初始化,则初始其父类
4.当jvm启动的时候,当用户指定执行一个主类(就是包含main的那个类),虚拟机会先初始化这个类.
上述的例子是因为满足第一条,执行static方法的时候编译器会生成invokestatic指令,这时候instance没有初始化,所以会执行Instance的构造方法,然后在return返回。
静态内部类模式
public class Instance1 {
private static class Holder{
private static Instance1 instance = new Instance1();
}
private Instance1(){
System.out.println("instance1 alloc");
}
public static Instance1 getInstance(){
System.out.println("instance1 called");
return instance;
}
}
静态内部类和饿汉模式都采用类装载的机制保证,当初始化实例的时候只有一个线程执行,保证了多线程下的安全。
JVM会在类初始化的阶段(类装载的阶段)创建一个锁,该锁保证多个线程同步执行类初始化的工作,因此在多线程环境下,类加载的机制依然是安全的。
饿汉模式 是启动就加载,造成资源浪费
静态内部类 只有在调用getInstance方法的时候 才会去装载内部类,从而完成实例的初始化,不造成资源的浪费。 为比较推荐的单例实现方式。