设计模式—单例模式

本文是关于设计模式中单例模式的 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

静态内部类

用静态内部类的方式实现单例类,利用了Java 静态内部类的特性

Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例

SingletonInner 是一个静态内部类,当外部类 Singleton 被加载的时候,并不会创建 SingletonInner 实例对象。

只有当调用 getInstance 方法时,SingletonInner 才会被加载,这个时候才会创建 instanceinstance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。

public class Singleton {
    public String value;

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

    private static class SingletonInner {
        private static final Singleton instance = new Singleton("harry");
    }

    public static synchronized Singleton getInstance(String value) {
        return SingletonInner.instance;
    }
}

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

image

枚举

用枚举来实现单例,是最简单的方式。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum Singleton {
    INSTANCE("Singleton instance");

    Singleton(String s) {}
}

测试代码如下

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

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

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

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

运行结果

image

posted @ 2023-12-12 10:35  Frodo1124  阅读(31)  评论(0编辑  收藏  举报