第一种 懒汉 线程不安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
//静态工厂方法
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
这种写法lazy loading很明显,等到第一次使用时再创建实例,因此称为懒汉。问题是该写法没有考虑线程安全问题,并发环境下可能会出现多个Singleton实例,在多线程下不能正常工作。但是提供了单例的基本思路:构造器私有。将构造器限制为private能够避免类在外部被实例化。
以下三种是对懒汉模式的改造,保证线程安全
第二种 懒汉 synchronized 保证线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
//静态工厂方法 采用同步
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
该写法能够保证线程安全,但是效率很低。不论instance是否被实例化,当调用getInstance获取实例的时候,都会加锁。
第三种 懒汉 双重校验锁
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {//第一次判断
synchronized (Singleton.class) {
if (instance == null)//第二次判断
instance = new Singleton();
}
}
return instance;
}
}
1.为何synchronized不加在getInstance方法上,而是移到方法体内?
将synchronized写到方法体内,是为了降低锁的粒度。考虑存在这样的情况:Singleton类中还会存在其他的static方法如f(),当synchronized直接加到getInstance方法上的时候,就会对整个Singleton类加锁,其他程序想要调用类中f(),无法执行只能等待getInstance方法执行完再调用。
将synchronized写进方法内,只有instance实例未被实例化的时候,才会将整个Singleton类进行加锁。所以将synchronized写到方法体内是为了不影响其他方法的调用。
2.为何会进行两次instance==null的判断?去掉第一个可以吗?去掉第二个可以吗?
去掉第一个也可以,但是不推荐。如果去掉第一个if判断,那么当调用getInstance方法的时候,无论instance是否被实例化,都会对整个类进行加锁,不去掉第一个的话,只有instance== null的时候,才会对整个类加锁。
去掉第二个不可以。很可能在第一个if判断的时候,instance== null成立,等到执行到同步块中的时候,instance可能已经被其他线程实例化了。所以需要再加一次判断。
3.关于volatile的作用?
volatile的作用是避免指令的重排序。初始化一个实例:instance= newInstance()在字节码中有四个步骤:
1.申请内存空间
2.初始化默认值
3.执行构造器方法
4.连接引用和实例
这四个步骤后面两个可能会出现重排序,1234或者1243,1243出现的结果就是造成未初始化完全的对象会被发布然后被使用。volatile可以禁止指令的重排序,从而避免这个问题。
第四种 懒汉 静态内部类
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
第五种 饿汉 非延迟加载
public class Singleton {
//加载类的时候会初始化static的instance,
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
instance在类装载时就被实例化,因此是天生安全。
第六种 饿汉
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
该种写法与第五种类似,同样都是在类初始化的时候创建静态对象,因此也是线程安全的。
第七种 枚举
public enum Singleton {
INSTANCE;
}
枚举实现单例模式,不仅可以避免多线程问题,还可以防止反序列化建立新对象。枚举单例模式有点类似饿汉模式,没有实现lazy loading。
饿汉和懒汉的区别:
饿汉保证类一旦加载完成,就把单例实例化,等到getInstance()时,单例已经存在。饿汉天生线程安全,因此可直接用于多线程。
懒汉顾名思义,lazy loading为 延迟加载,等到getInstance()时,再去实例化单例。
懒汉本身不是线程安全的,但是可以通过加同步,静态内部类,双重锁校验的方式,人为实现线程安全。
资源加载的性能:
饿汉模式下当类加载完成后,也完成了单例的实例化,单例就会存在于内存中,不管后面会不会用到,都会占用一定内存。如果该单例占用资源很多的情况下,采用饿汉模式,会导致性能降低。但是由于单例已经初始化完成,因此在第一次调用的时候速度会非常快。
懒汉模式下,延迟加载,对于占用很多资源的单例比较实用。等到使用该单例时再进行加载,会导致性能延迟。
从速度和反应角度看,饿汉模式较优;从资源利用率看,懒汉模式较优。