设计模式(一):单例模式

116 阅读4分钟

IMG_8896.HEIC.JPG

1 前言

转眼已经到2022年了,祝大家虎年大吉。

从今天其我们开始设计模式相关的的内容,我们会在后续的文章中,陆续的介绍下23种设计模式,设计模式 的文章我们以单例模式开始。

单例模式是设计模式中类结构最为简单的设计模式之一,单例模式是创建型模式中的一种。 这种设计模式是为了保证一个类在全局只能有一个实例化对象。

单例模式的实现步骤如下:

  • 私有化构造方法
  • 对外提供获取实例的方法

2 实现方式

2.1 饿汉式

饿汉式的单例模式,顾名思义,这个实现方式不会考虑这个对象是否需要,都会实例化一个对象, 比较浪费资源。但这种实现方式比较简单,也是线程安全的一个单例实现。

/**
 * 饿汉式单例
 *
 * @author: XieHua
 * @date: 2022-01-04 15:15
 */
public class HungerSingleton {
    private static HungerSingleton instance = new HungerSingleton();
​
    private HungerSingleton() {
​
    }
​
    public static HungerSingleton getInstance() {
        return instance;
    }
}

2.2 懒汉式

要解决饿汉式浪费资源的问题,便有了懒汉式的实现方式,这种实现方式是当我们需要用到该对象时再去实例化。

/**
 * 非线程安全的懒汉式单例模式
 *
 * @author: XieHua
 * @date: 2022-01-04 15:17
 */
public class LazyUnsafeSingleton {
    private static LazyUnsafeSingleton instance;
​
    private LazyUnsafeSingleton() {
​
    }
​
    public static LazyUnsafeSingleton getInstance() {
        if (instance == null) {
            instance = new LazyUnsafeSingleton();
        }
        return instance;
    }
}

上面是一个简单的懒汉式单例模式的实现代码,但这种实现方式不是线程安全的,为了解决线程安全我们很容易 想到进行加锁,可修改如下:

/**
 * 同步的单例
 *
 * @author: XieHua
 * @date: 2022-01-04 15:20
 */
public class SyncSingleton {
    private static SyncSingleton instance;
​
    private SyncSingleton() {
​
    }
​
    public synchronized SyncSingleton getInstance() {
        if (instance == null) {
            instance = new SyncSingleton();
        }
​
        return instance;
    }
}

这种方式虽然解决了线程安全的问题,但每次调用getInstance方法时都需要获取锁,这种方式的性能不是 很好,我们可以使用双重检查锁或者静态内部类的方式进行实现,代码如下:

/**
 * 双重检查的单例模式
 *
 * @author: XieHua
 * @date: 2022-01-04 15:26
 */
public class DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance;
​
    private DoubleCheckSingleton() {
​
    }
​
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
​
/**
 * 静态内部类  线程安全
 *
 * @author: XieHua
 * @date: 2022-01-04 15:23
 */
public class StaticInnerClassSingleton {
    private static StaticInnerClassSingleton instance;
​
    private StaticInnerClassSingleton() {
​
    }
​
    public static StaticInnerClassSingleton getInstance() {
        return StaticInnerClassSingletonHolder.singleton;
    }
​
​
    private static class StaticInnerClassSingletonHolder {
        public static StaticInnerClassSingleton singleton = new StaticInnerClassSingleton();
    }
}

2.3 避免单例破坏

通过上面代码我们介绍了饿汉式、懒汉式及线程安全的懒汉式的单例实现,这样的代码能完全的避免单例不被 破坏吗?我们创建对象的方式除了使用new关键字外,还可以通过反射和序列化进行创建,很明显我们上面 的单例实现方式可以通过反射和序列化进行破坏,这里我们介绍下如何解决:

针对反射破坏单例的问题,我们可以在构造函数中检查是否已经实例话了,如果已经实例化了就抛出异常,在 构造方法中增加如下代码:

if (instance != null) {
    throw new RuntimeException();
}

针对序列化破坏单例的问题,我们可以在单例类中增加readResolve方法,代码如下:

private Object readResolve(){
    return instance;
}

2.4 枚举实现单例

使用枚举实现单例是非常简单的,并且这种实现方式可以避免通过反射和序列化对单例的破坏,代码如下:

/**
 * Create By IntelliJ IDEA
 *
 * @author: XieHua
 * @date: 2022-01-04 15:30
 */
public enum EnumSingleton {
    INSTANCE;
}

通过枚举的实现单例的代码是不是很简单,这也是比较推荐使用的方式。

3 总结

在本篇文中我们介绍单例模式的几种实现方式,并介绍了如何解决通过反射和序列化破坏单例的问题。

  • 饿汉式:实现简单,线程安全;无论是否需要都会创建对象,浪费资源; 可以通过反射和序列化进行破坏

  • 懒汉式:使用时才创建对象;需要考虑线程安全问题;可以通过反射和序列化破坏单例

    • 同步方法
    • 双重检查锁
    • 静态内部类
  • 枚举实现:实现简单;线程安全;不可通过反射和序列化进行破坏

欢迎关注我的公众号【Bug搬运小能手】,持续更新多线程、Spring、redis、消息队列等Java相关知识