饿汉式

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

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
package singleton

type singleton struct {
    name string
}

var instance = &singleton{name: "myblog"}

func GetInstance() *singleton {
    return instance
}

同步懒汉式

public final class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

        return instance;
    }
}
package singleton

import "sync"

type singleton struct {
    name string
}

var (
    once     sync.Once
    instance *singleton
)

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{name: "myblog"}
    })
    return instance
}

[!blue]

  • Java:同步创建。 synchronized 让判空和创建过程一次只由一个线程执行,避免重复创建实例;实例创建后,每次获取仍会进入同步方法。
  • Go:只初始化一次。 sync.Once 只执行一次初始化函数,其他并发调用会等待它完成,再返回实例。
  • Go 的注意点:不会自动重试。 初始化函数即使发生 panic,后续 Do 调用也不会重新执行它。

双重检查锁(DCL)

public final class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

        return instance;
    }
}

[!blue]

  1. 锁外判空:减少加锁。 实例已经存在时,直接返回,不再进入同步块。
  2. 锁内判空:避免重复创建。 当前线程等待锁期间,其他线程可能已经创建实例,所以获得锁后必须再检查一次。
  3. volatile:安全发布实例。 让锁外读取的线程也能看到实例及其已完成的初始化结果,不能省略。此写法适用于 Java 5 及之后的内存模型。

静态内部类

public final class Singleton {
    private Singleton() {}

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

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

[!blue]

  • 延迟创建。 初始化外部类 Singleton 不会初始化 Holder;第一次访问 Holder.INSTANCE 时才创建实例。
  • 并发安全。 JVM 负责协调 Holder 的类初始化,保证初始化完成后其他线程再使用实例,无需手写 synchronized 或 volatile。

枚举

public enum Singleton {
    INSTANCE;

    public void execute() {
        System.out.println("执行任务");
    }
}

[!blue]

  • 调用方式。 使用 Singleton.INSTANCE.execute(),无需提供 getInstance()。
  • 实例身份。 Java 禁止通过反射调用枚举构造方法;反序列化时也会返回对应的枚举常量,不会创建另一份实例。
  • 并发边界。 枚举保证实例身份,但不会自动保护实例中的可变数据;业务方法修改共享状态时,仍需处理并发访问。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2020/50659901
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!