什么是单例模式
单例模式是一种创建型设计模式,用来创建一个全局唯一的对象。定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用场景
整个程序中有且只有一个实例化对象的类,考虑使用单例模式。用来保存一个全局使用的对象,避免频繁的销毁和创建。可以用来协调和保存全局变量和资源。
实现代码
饿汉模式
饿汉模式:对象已经创建,拿来即用。在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。优点:不存在多线程问题。缺点:类加载慢,不用时占用内存。
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
懒汉模式
懒汉模式:类加载时不初始化,使用的时候才初始化。优点:类加载快、不使用时不占用内存。缺点:需要考虑线程同步问题。
线程不安全
public class Singleton {
private static Singleton instance = null;
publicstatic Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
基于饿汉式进行改造,初始化动作放到getInstance()中,第一次使用时进行初始化。缺点:线程不安全。比如instance =null时,两个线程同时执行到判断instance == null都为true,都会进入判断内部进行两次的初始化动作。
synchronized方法
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
在第一种的基础上,getInstance方法加上synchronized保证一次只有一个线程进入,保证了线程安全。但是,这种方式每次使用getInstance时都要加锁,影响性能。
DCL双重检验
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
既然对整个方法加锁影响效率,那么就对第一次初始化的时候加锁就好了,把synchronized放到instance=null时判断。
注意:
- 为什么进入加锁代码块后还要做一次判断?
两个线程可能进入synchronized,一个进入了,另外一个在等待,第二个线程进入后如果不做判断会重复初始化。 - 依然线程不安全?
因为new一个对象时,并不是原子操作。其中包括分配内存空间、初始化成员变量、引用指向分配的内存区域三个动作,指令重排序导致三个指令调用顺序不一致。比如第一个线程正在new Instance操作,指令重排序后已经分配了引用,第二个线程调用getInstance判断是否为空,此时已经分配了引用,不为空直接返回了,但是由于new还没有完成直接抛出异常。
DCL双重检验+volatile
public class Singleton {
private static volatile Singleton instance = null;
public static synchronized Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
加上volatile 关键字,禁止指令重排序。
静态内部类
public class Singleton {
private static volatile Singleton instance = null;
private static class SingleHolder{
private final static Singleton INSTANCE = new Singleton();
}
public static synchronized Singleton getInstance() {
return SingleHolder.INSTANCE;
}
}
JVM加载外部类时不会加载内部类,实现懒加载。
枚举类
public enum Singleton {
INSTANCE;
}
不仅可以解决线程同步,还可以防止反序列化(class可以通过方式反射的方式创建一个对象,导致全局对象不唯一,枚举类防止反序列化的原因是没有构造方法,反射得到的是枚举值还是同一个对象)。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。