定义
保证一个类只有一个实例,并且提供全局访问点。
场景
重量级对象,不需要多个实例:例如线程池,数据库连接池。
懒汉模式
延迟加载,只有在真正使用的时候才开始实例化。
class LazySingleTon {
private volatile static LazySingleTon instance;
private LazySingleTon(){
}
public static LazySingleTon getInstance() {
if (instance == null) {
synchronized (LazySingleTon.class) {
if (instance == null) {
instance = new LazySingleTon();
}
}
}
return instance;
}
}
关键点:
- 既然只能有一个实例,那么就不允许外部new这个对象,因此将构造函数设置为private;
- 既然没有外部对象,那么就需要将getInstance设置为静态方法,静态方法内部只能访问静态变量和静态方法,因此instance也必须是静态的,再加上没有不给外部访问,因此是private;
- 要解决多线程问题,就要用到sychronized,之所以有两个instance == null的判断是因为
- 第一个判断是为了提升性能,当有instance的时候直接返回即可,不用涉及到锁的争夺;
- 第二个判断是为了安全,如果不加的话,
- 第一个线程进来,new对象,释放锁,停住;
- 第二个线程进来,new对象,释放锁;
- 导致new了两个对象,不符合单例。
- 此外,需要加上volatile来防止指令重排;
总结:
- 懒汉模式比较麻烦;
- 需要考虑线程安全问题;
- 需要double check 该 instance是否存在,容易忘记;
- 需要加volatile防止指令重排;
饿汉模式
不管用不用,我先将实例new出来;
class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
关键点:
- 和懒汉一致的点:
- 构造函数是private;
- getInstance是static;
- instance是private static;
- 在类初始化的时候直接将instance给new出来;
- 因为类只会被加载一次,静态变量的赋值是在初始化过程中完成的,通过该方式保证了线程安全的问题。
静态内部类
有点像懒汉+恶汉。
class InnerClassSingleton {
private static class InnerClassHolder {
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton() throws Exception {
if (InnerClassHolder.instance != null) {
throw new Exception("不允许多个实例");
}
}
public static InnerClassSingleton getInstance() {
return InnerClassHolder.instance;
}
}
关键点:
- 该类加载的时候不会去初始化示例;
- 但是在调用getInstance方法时,因为要访问InnerClassHolder的instance属性,因此会去进行内部类的加载和初始化,这一步由JVM来保证线程安全。
注意,在构造函数中加判断是为了防止反射攻击,导致仍然可以new出多个实例。
枚举
枚举的优点就是能抵抗反射攻击。