单例模式

软件设计模式
2021-05-17 14:29 · 阅读时长6分钟
小课

单例模式是开发中经常用到的一个设计模式,也是最容易理解的设计模式,它的主要目的是限制类的实例化,保证该类在Java虚拟机中只有一个实例存在。虽然单例模式很简单,但是实现单例模式的方式有很多,而且不同的实现方式有各自的优缺点,下面就介绍一些常见的实现单例模式的方法。

饿汉模式 Eager initialization

饿汉模式在该类加载时就创建实例,优点是实现非常简单,但是如果该单例前期不用的话,提前实例化就占用了资源,特别需要注意如果单例资源比较重的话,应该考虑到这一点,另外一个缺点是这种方式没有做异常处理。

public class Singleton {
    
    private static final Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton getInstance() {
        return instance;
    }
}
静态代码块初始化 Static block initialization

这种方式和饿汉模式一样,也是在该类加载时就创建实例,也会浪费资源,但是它比饿汉模式好的是有异常处理

1public class Singleton {
2
3    private static Singleton instance;
4
5    private Singleton() { }
6
7    static {
8        try {
9            instance = new Singleton();
10        } catch (Exception e) {
11            //handle exception
12        }
13    }
14
15    public static Singleton getInstance() { return instance; }
16}
懒汉模式 Lazy Initialization

懒汉模式只会在需要使用单例时才会实例化,避免了浪费资源的问题

1public class Singleton {
2
3    private static Singleton instance;
4
5    private Singleton() { }
6
7    public static Singleton getInstance() {
8        if (instance == null) {
9            instance = new Singleton();
10        }
11        return instance;
12    }
13}

懒汉模式虽然避免了资源浪费,但是它存在线程安全问题,在单线程的环境中使用没问题,但是多线程环境下,可能会出现多个实例的情况。

线程安全的懒汉模式 Thread Safe Lazy Initialization

要保证线程安全,我们可以给获取单例的方法加上锁。

1public class Singleton {
2
3    private static Singleton instance;
4
5    private Singleton() { }
6
7    public static synchronized Singleton getInstance() {
8        if (instance == null) {
9            instance = new Singleton();
10        }
11        return instance;
12    }
13}

但是这样又带来了一个小问题,就是性能问题,因为每次获取单例都需要获取同步锁,而我们其实只有在初始化单例的时候需要保证同步,为了解决这个问题,就出现了双重检查锁的实现方式。

双重检查锁模式 Double Checked Locking

这样就只有在初始化单例的时候才需要获取同步锁来保证线程,在初始化完成之后,就不需要再获取同步锁,减少了对性能的影响。

1public class Singleton {
2
3    private static volatile Singleton instance;
4
5    private Singleton() { }
6
7    public static synchronized Singleton getInstance() {
8        if (instance == null) {
9            synchronized (Singleton.class) {
10                if (instance == null) {
11                    instance = new Singleton();
12                }
13            }
14        }
15        return instance;
16    }
17}
私有静态内部类模式  Private Inner Static Class

首先我们要知道,静态内部类默认是不会跟随外部类一起加载的,只有在使用到了静态内部类时,才会加载它,而且这个过程是线程安全的。

1public class Singleton {
2
3    private Singleton() { }
4
5    private static final class InstanceHolder {
6        private static final Singleton instance = new Singleton();
7    }
8
9    public static synchronized Singleton getInstance() {
10        return InstanceHolder.instance;
11    }
12}

这样就只有在调用到getInstance时,才会加载InstanceHolder类,然后初始化单例。

枚举类模式 Enum

枚举类的构造方法是私有的,而且Java虚拟机保证枚举类的值只会初始化一次,使用非常简单,但是它的缺点是无法懒加载,只要加载了枚举类就会实例化单例。

public enum Singleton {
    INSTANCE;

    public Singleton getInstance() {
        return INSTANCE;
    }
}
单例singleton pattern懒汉饿汉线程安全java