【问题标题】:Is this a good implementation for a Thread Safe Lazily Loaded Singleton in Java?这对于 Java 中的线程安全延迟加载单例来说是一个很好的实现吗?
【发布时间】:2014-06-26 15:29:35
【问题描述】:

我想用 Java 实现线程安全的延迟加载单例。

这是一个好的设计吗?

public final class ThreadSafeLazySynchronizedSingleton {

private ThreadSafeLazySynchronizedSingleton(){}

private static ThreadSafeLazySynchronizedSingleton instance;

public static ThreadSafeLazySynchronizedSingleton getSynchronizedInstance(){
    synchronized(ThreadSafeLazySynchronizedSingleton.class){ 
        if (instance==null){
            instance = new ThreadSafeLazySynchronizedSingleton();
        }
        return instance;
    }
}

}

我还想创建一个无锁版本,用于一个线程比其他线程访问 Singleton 的次数更多的场景,我将如何使用 Java 中的原子对象来实现这一点,即 AtomicBoolean 或 AtomicReference 等。能否提供一个代码示例?

【问题讨论】:

  • 您也可以将整个getSynchronizedInstance()方法设为synchronized,而不是自己显式获取锁。
  • 当然enum 会更简单,更快;)
  • @PeterLawrey 更不用说这是实现单例模式的推荐方式!

标签: java multithreading singleton lazy-initialization


【解决方案1】:

更好的设计应该是

public final class ThreadSafeLazySynchronizedSingleton {

    private ThreadSafeLazySynchronizedSingleton(){}

    private static volatile ThreadSafeLazySynchronizedSingleton instance;

    public static ThreadSafeLazySynchronizedSingleton getSynchronizedInstance(){
        if (instance==null)
            synchronized(ThreadSafeLazySynchronizedSingleton.class){ 
                if (instance==null){
                    instance = new ThreadSafeLazySynchronizedSingleton();
                }
        return instance;
        }
    }
}

但是,恕我直言,最好的设计是

public enum ThreadSafeLazySynchronizedSingleton {
    INSTANCE;
}

【讨论】:

  • 枚举如何在幕后工作,以便它们可以用作单例?
  • @aranhakki An enum 按设计列出了所有可能的实例。如果您指定一个实例,则您有一个单例。它是线程安全的,并且类是延迟加载的。
  • 为什么要检查instance是否为null两次?
  • @ericGnuLuver 因为另一个线程可能在当前线程获取锁时初始化了单例。
  • 那么如果获取锁的线程遇到这种情况,它会收到与其他线程初始化相同的实例吗?
【解决方案2】:

在 Java 中实现单例设计模式的推荐方法是使用枚举方法。 Enum 方法非常简单且隐式线程安全。

public enum EnumTest {
  INSTANCE;

  // Test code
  public static void main(String[] args) {

      // First thread
      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
             EnumTest obj = EnumTest.INSTANCE;
         }   
      });
      t1.start();

      // Second thread
      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
             EnumTest obj = EnumTest.INSTANCE;
         }   
      });  
      t2.start();
   }
 }

不错的教程here.

【讨论】:

    【解决方案3】:

    最简单的事情可能会像你需要的那样懒惰:

    private static ThreadSafeLazySynchronizedSingleton instance
        = new ThreadSafeLazySynchronizedSingleton();
    
    public static ThreadSafeLazySynchronizedSingleton instance() {
        return instance;
    }
    

    没错,instance 将在类加载后立即创建。但是该类在使用之前不会被加载,此时您可能需要获取实例。

    如果ThreadSafeLazySynchronizedSingleton 类也有不使用instance 的静态辅助方法,这将不会像您需要的那样懒惰,但这表明它们与班上的其他人,可能应该搬到他们自己的班级。

    很多人还对单例使用单值enum。我个人觉得它很丑,但它确实有一些优点(为你处理私有构造函数和instance,即使你的类是可序列化的,也会强制执行单例,否则这不是微不足道的)。

    【讨论】:

    • 我认为同步块中的变量已为您同步,因此更改会在退出时发布到共享内存,并在进入时读取最新版本,因此我的 volatile 关键字不需要执行?非最终的也应该没问题,因为它是私有的?你的实现不应该需要同步块或 volatile 变量吗?
    • @aranhakki 哦,你是对的——你所拥有的根本不是双重检查锁。我误读了这个问题。但是我的实现不需要同步/易失性,因为final 修饰符还提供了发生前的保证(有些有限,但足以用于此用途)。
    • 啊,好吧,我不知道最终的关键字,请为我发现你的错误点赞,我可以加分!
    【解决方案4】:

    如果您想要一个真正的无锁实现,则需要一些旋转。否则@yshavit 或@Peter Lawrey 是最好的答案。

    无锁:

    private static final AtomicBoolean created = new AtomicBoolean(false);
    private static volatile LazySingleton instance = null;
    
    public static ThreadSafeLazySynchronizedSingleton getIntsance(){
       if(instance != null) return instance.get();
    
       if(created.compareAndSet(false, true)){
          //only one thread can try and create
          instance = new LazySingleton();
       } else {
          //other thread is creating, spin until ready
          while(instance == null);
       }
       return instance;
    }
    

    【讨论】:

    • 它如何发送不同的引用?只有一个会被创建和分配。我选择不使用 AtomicReference 大概是因为懒惰地阻止创建多个。使用 AtomicReference,您可以创建多个(即:1. 创建实例 2. 尝试在原子引用中设置该实例)。
    • 只能创建一个实例,但是这个不安全的。想象一下有人在 CAS 之后但在created.set(true) 之前进来。此时,created.get() == true(由于 CAS),所以第二个调用者返回 instance。但是在created.set(true) 提供HB 边缘之前他们这样做了(相对于他们看到的created.get()),所以return instanceinstance = new LazySingleton() 之间没有传递HB。这意味着第二个线程仍然可以看到null 或部分构造的对象。
    • 它还在,在第一行。顺便说一句,created.get() != null 你是什么意思?它不会编译(created.get() 返回boolean)。我认为你需要完全摆脱第一行,然后做 CAS-else-spin。 (最后一行必须是 return instance.get(),如果需要,您也可以使用 CAS 中的本地引用之一或旋转。)
    • 你是对的,我没有仔细阅读第一个版本。
    猜你喜欢
    • 2023-03-06
    • 2021-05-12
    • 1970-01-01
    • 1970-01-01
    • 2013-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多