设计模式——单例模式
设计模式——单例模式
单例模式,顾名思义就是一个类只能有一个实例。单例模式根据实例的创建的时间大致可以分为两类——饿汉式单例和懒汉式单例。
饿汉式单例
饿汉式单例,是指在类初始化的时候就创建实例,这样做有一个好处,就是保证在获取实例的时候可以保证线程安全而且还简单,即多个线程获取到的都是同一个实例。但这样做也有一个缺点,就是即使不用实例,实例也会创建,这样就会造成内存浪费。饿汉式单例的简单实现:
// 饿汉式单例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表来实现单例在获取单例时还是会出现线程安全问题。
来源https://www.cnblogs.com/funtirn/p/15426201.html