设计模式—单例模式

发布时间 2023-12-12 10:38:11作者: Frodo1124

本文是关于设计模式中单例模式的 Java 代码实现详解

懒汉式

public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        this.value = value;
    }

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

测试代码

class test{
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance("FOO");
        Singleton another = Singleton.getInstance("BAR");

        System.out.println(singleton.value);
        System.out.println(another.value);
    }
}

输出

image

但是这种方法在多线程的使用场景下是不安全的,有可能会产生多个单例实例,比如把测试代码改成下面的形式

class test {
    static class ThreadFoo implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("FOO");
            System.out.println(singleton.value);
            System.out.println("In Foo, " + singleton);
        }
    }

    static class ThreadBar implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("Bar");
            System.out.println(singleton.value);
            System.out.println("In Bar, " + singleton);
        }
    }

    public static void main(String[] args) {
        Thread threadFoo = new Thread(new ThreadFoo());
        Thread threadBar = new Thread(new ThreadBar());

        threadFoo.start();
        threadBar.start();
    }
}

运行结果就会发生变化

image

饿汉式

多线程场景下安全使用单例模式的一种方法是饿汉式

public class Singleton {
	// 在类加载的时候就创建并初始化
    private static Singleton instance = new Singleton("harry");
    public String value;

    private Singleton(String value) {
        this.value = value;
    }

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

测试代码不变,运行结果如下

image

可以看到,两个实例的 hashcode 是相同的,是同一个对象。但是使用这种方法需要在类加载的时候就初始化实例,占用内存资源,不推荐使用这种方法。

双重检测

双重检测是目前常用的线程安全单例模式的方式,它有两点改变:

  • 在实例上加关键字 volatile ,防止指令重排序,因为指令重排序可能会导致 Singleton 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化,就被另一个线程使用了
  • getInstance 方法中对创建实例的代码加锁,保证只有一个线程能创建示例
public class Singleton {
    // 加入 volatile 关键字防止指令重排
    private static volatile Singleton instance;
    public String value;

    private Singleton(String value){
        this.value = value;
    }

    public static synchronized Singleton getInstance(String value){
        if(instance == null){
            // 加类级别的锁
            synchronized(Singleton.class){
                // 避免多线程并发时多次创建对象
                if(instance == null){
                    instance = new Singleton(value);
                }
            }
        }
        return instance;
    }
}

测试代码不变,运行结果如下

image

静态内部类

枚举