单例模式解析:
单例模式定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。
好处:
1.减少内存的占用
2.避免频繁的创建销毁对象,可以提高性能
3.避免对共享资源的多重占用
4.可以全局访问
饿汉式:
public class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
***饿汉式线程安全,不足的地方是不能实现延迟加载
懒汉式:
public class LazySingleton{
private static LazySingleton lazySingleton = null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
***在多线程环境中,可能会被创建出多个实例,线程不安全;因此设置getInstance()方法为同步方法,保证线程安全。但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁双重锁模式。
双重锁模式:
public class SingletonLock{
private static volatile SingletonLock singletonLock= null;
private SingletonLock(){}
public static SingletonLock getInstance(){
if(singletonLock == null){ // single check
synchronized(SingletonLock.class){//静态方法使用的锁需要是class,不能是this
if(singletonLock == null){ // double check
singletonLock = new SingletonLock();
}
}
}
return singletonLock;
}
}
***并非完美的代码,singletonLock = new SIngletonLock()并非原子操作,事实上JVM中这句话大概做了三件事:
1.给singletonLock 分配内存
2.调用SIngletonLock的构造函数初始化成员变量
3.将instance对象指向分配的内存空间(执行完此步singletonLock非null)
由于JVM的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。因此将singletonLock声明为volatile禁止指令重排序优化
静态内部类:
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
***这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
枚举:
public enum EasySingleton{
INSTANCE;
}
***我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。