设计模式-单例模式(Java实现)

109 阅读2分钟

单例模式是一种非常经典的设计模式,主要是为了保证一个类只有一个实例,控制资源的消耗。比如Spring容器的实例对象和数据库连接池的实例,都是为了可以复用而避免创建更多的对象。 单例模式有两种实现:一是懒汉式;二是饿汉式。

1. 懒汉式,线程不安全

这种方式在类加载时不初始化。在需要的时候才创建对象,节约资源。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2. 懒汉式,线程安全

getInstance()方法上使用synchronized关键字加同步锁。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

3. 饿汉式

类加载时就创建好实例对象,保证了线程安全。

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

    private Singleton() {}

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

4. 双重检查锁

有同学会很纳闷这种方式为什么需要volatile关键字来修饰成员变量,这个就涉及到JUC了,其实就是Java多线程的一些底层原理。volatile关键字保证了可见性和防止指令重排。 在双重检查锁定的单例模式实现中,如果不使用volatile,可能会发生以下情况:

  1. 线程A进入getInstance()方法,并执行到synchronized块内部。
  2. 线程A执行new Singleton(),这涉及到三个操作:分配内存、初始化对象、将对象引用指向分配的内存地址。
  3. 由于指令重排序,对象初始化可能在对象引用赋值之前完成,但线程A的写入操作(将对象引用指向分配的内存地址)可能还没有同步到主内存。
  4. 线程B进入getInstance()方法,并检查singleton是否为null。由于线程A的写入操作还没有同步到主内存,线程B可能会看到一个null值,然后线程B也会尝试创建一个新的Singleton实例。

使用volatile关键字可以防止这种情况,因为volatile会确保对singleton变量的写入操作会立即同步到主内存,并且其他线程读取singleton变量时会从主内存读取最新值。

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

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

5. 静态内部类

这种方式在类加载时,因为内部类是不会被加载的,只有通过外部类访问内部类成员时才会被加载,所以很好的实现了延迟加载的特性,同时又保证了线程安全。

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}