单例的目的是为了保证运行时Singleton类只有唯一的一个实例,用于一些较大开销的操作。
实现单例模式的方式
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
线程安全,由于使用static关键字进行了修饰,只能获取到一个对象,从而达到了单例。并且在Singleton类初始化的时候就创建了对象,加载到了内存。
问题:在没有使用这个对象的情况下就加载到内存是一种很大的浪费。
针对这种情况,有一种新的思想提出——延迟加载,也就是所谓的懒汉式。
懒汉式
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
问题:多线程访问情况下,很有可能会产生多个实例对象,导致线程安全问题。
方案一:
使用同步的方法解决这个问题,加上synchronized关键字,代码如下:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
问题:多线程访问情况下,同步方法会导致系统性能下降。
方案二:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
问题:创建对象时会进行以下操作
- 分配内存空间
- 初始化对象
- 将内存地址赋值给变量
在这三个步骤中,极有可能会将2、3操作进行重排序。在重排序情况下,还没有初始化对象,就先将内存地址赋值给了变量。多线程情况下,后面线程执行判断该对象不为空,直接使用发现对象可能还没有进行初始化操作。存在不能正常运行的风险
使用Java volatile关键字避免重排序,可以解决双重锁检查的问题。但是,使用volatile的性能开销也会有所上升。同时,多线程并发下加了锁还是会有性能问题。
// 双重检查
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
终极方案:静态嵌套类
public class Singleton {
private Singleton() {}
pirvate static class SingletonHolder {
public final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}