单例模式解决了什么问题
一个全局使用的变量反复创建和销毁
单例模式的使用场景
-
Web的配置文件
-
数据库连接池
-
多线程的线程池
-
Spring的Bean
-
Web的日志
实现方式 1.0:懒汉
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒加载。线程不安全。单线程场景性能好。
实现方式 1.1:懒汉加锁
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒加载。线程安全。性能差,因为每一次获得对象都要加锁。
实现方式 2.0:饿汉
class Singleton {
private static Singleton instance = new Singletion();
private Singleton() {}
public static synchronized Singleton getInstance() {
return instance;
}
}
非懒加载。线程安全。同上,性能差。
实现方式 3.0:DCL(Double-Check Locking)
public final class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
public Object readResolve(){
return singleton;
}
}
懒加载。线程安全。高性能。
-
高效的原因在于只对第一次创建对象用了锁,后续获得对象无锁。
-
使用synchronized是因为多线程情况下,可能多个线程判断对象为null,所以同步创建对象的代码,一次保证只有一个线程进。
-
第二次判定对象是否为空是因为,当第一个进入的线程创建好对象之后,后续线程不需要重复创建。
-
synchronized已经可以保证原子性和可见性,使用votatile是为了对付指令重排序。
创建对象要三步:
- 分配空间
- 初始化对象
- 把空间地址给引用
如果线程A在创建对象的时候,编译器为了优化进行了指令重排序,将创建对象的步骤变为:
- 分配空间
- 把空间地址给引用
- 初始化对象
那么可能出现,线程A在第二步结束的时候,还未初始化对象,线程BCD已经在请求获得对象了,这个时候引用已经被赋值,所以会直接返回instance,但是此时对象依然为空。
DCL相关问题
-
为什么类要加final?
防止子类重写get方法
-
如果实现了序列化接口,要怎么防止反序列化破坏单例?
加入readResovle方法
-
为什么构造方法要私有?这样能阻止反射创建新对象吗?
防止外界创建单例模式类的对象,只能类中调用get方法才能创建实例。
不能阻止,反射本质是获得Class对象,也就是C++里的instanceKlass,相当于直接获得这个类的类对象,直接修改访问修饰符就能把private变成public,根本不是一个维度的对手。
-
下列代码能保证线程安全吗?
private volatile static Singleton singleton = new Singleton();
当然能,静态对象初始化是在类加载时候的,和线程安全能发生的时间完全不重合。
实现方式 4.0:枚举单例模式
Effective Java中说的最好的单例模式实现方式。
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
枚举单例模式相关问题
-
枚举单例是怎么限制实例个数的?
从反编译中可以看出来,枚举类中的成员变量都是static,所以实例唯一。
-
枚举单例创建的时候是否有并发问题?
因为是static实例,所以创建的时候是类加载阶段,避免指令重排序等问题!无并发问题。
-
枚举单例能否用反射破坏?
不可以。反射在用newInstance创建对象的时候,会检查是否是ENUM修饰的,如果是就抛出异常。
-
枚举单例能不能用反序列化破坏单例?
不可以,枚举类Enum实现了readObject方法,一旦调用反序列化,会抛出以下异常。
throw new InvalidObjectException("can't deserialize enum");
-
枚举单例是懒汉式还是饿汉式?
饿汉,底层是静态变量,所以不是懒加载。 «««< HEAD =======
5923cbf3cea8405b5e4eeb936edd4afdcea7f87c