单例模式解析:

单例模式定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。

好处:

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,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

results matching ""

    No results matching ""