设计模式——单例模式

设计模式——单例模式

单例模式,顾名思义就是一个类只能有一个实例。单例模式根据实例的创建的时间大致可以分为三类——饿汉式单例、懒汉式单例和容器式单例。

饿汉式单例

饿汉式单例,是指在类初始化的时候就创建实例,这样做有一个好处,就是保证在获取实例的时候可以保证线程安全而且还简单,即多个线程获取到的都是同一个实例。但这样做也有一个缺点,就是即使不用实例,实例也会创建,这样就会造成内存浪费。饿汉式单例的简单实现:

// 饿汉式单例
class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return instance;
    }
}

懒汉式单例

懒汉式单例,见名知意,只有在需要的时候才会创建实例,直接看代码:

// 懒汉式单例
class LazySingleton {
    private static LazySingleton singleton;

    private LazySingleton() {

    }

    public LazySingleton getInstance() {
        if (singleton == null) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

很显然懒汉式单例解决了,饿汉式单例可能出现的占用内存的情况,但是饿汉式单例同样带来了获取单例时线程不安全的问题,即可能出现多个线程取到的实例不是同一个,最直接的解决方案就是加锁。

class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton singleton;

    private SynchronizedLazySingleton() {

    }

    public synchronized SynchronizedLazySingleton getInstance() {
        if (singleton == null) {
            singleton = new SynchronizedLazySingleton();
        }
        return singleton;
    }
}

但是加锁后会使代码性能变差。所以我们需要对上面的代码做个优化,采用volatile和synchronized配合的方式:

class DoubleLockSingleton {
    // 通过volatile修饰来确保singleton的状态改变在所有线程间可见
    volatile private static DoubleLockSingleton singleton;

    private DoubleLockSingleton() {

    }

    public static DoubleLockSingleton getInstance() {
        if (singleton == null) {
            synchronized (DoubleLockSingleton.class) {
                if (singleton == null) {
                    singleton = new DoubleLockSingleton();
                }
            }
        }
        return singleton;
    }
}

但是这样对性能的提升有限,我们可以换一个思路,通过内部类的初始化来优化代码

class InnerClassSingleton {
    private InnerClassSingleton() {

    }

    // 静态内部类只有在使用的时候才会初始化
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.singleton;
    }

    private static class SingletonHolder {
        private static final InnerClassSingleton singleton = new InnerClassSingleton();
    }
}

这样就可以达到性能与线程安全的平衡。

注册式单例

序列化会使单例失效:有时我们需要将单例序列化后写入磁盘,但是一旦从磁盘中把单例反序列化出来,由于单例的内存地址变了,这样实际上就是创建了一个新的实例,于是只有一个实例的原则就被破坏了。通过枚举类的特性来实现注册式单例:

enum EnumSingleton {
    SINGLETON;
    
    public EnumSingleton getInstance() {
        return SINGLETON;
    }
}

通过枚举类来实现单例模式,可以保证序列化后得到的是同一个对象,而且枚举类的实例不能通过反射来创建。同样我们也可以通过hash表来实现注册式单例:

class HashMapSingleton{
    private static final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();

    private HashMapSingleton(){

    }

    public static Object getInstance(String className) {
        if (!map.containsKey(className)) {
            Object obj = null;
            try {
                obj = Class.forName(className).newInstance();
                map.put(className, obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        }
        return map.get(className);

    }
}

但是通过hash表来实现单例在获取单例时还是会出现线程安全问题。

上一篇:请你说一下双重校验锁实现对象单例(请你解释一下双重校验锁实现对象单例)—java并发知识


下一篇:设计模式之单例模式