java - Java中的单例和多线程

标签 java multithreading design-patterns singleton

在多线程环境中使用 Singleton 类的首选方式是什么?

假设如果我有 3 个线程,并且它们都尝试同时访问单例类的 getInstance() 方法 -

  1. 如果不保持同步会怎样?
  2. 使用 synchronized getInstance() 方法还是在 getInstance() 中使用 synchronized block 是好习惯吗.

请告知是否有其他出路。

最佳答案

如果您说的是 threadsafe , lazy initializationsingleton ,这是一个很酷的代码模式,它可以在没有任何同步代码的情况下完成100% 线程安全的延迟初始化:

public class MySingleton {

     private static class MyWrapper {
         static MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return MyWrapper.INSTANCE;
     }
}

这将仅在调用 getInstance() 时实例化单例,并且它是 100% 线程安全的!很经典。

之所以有效,是因为类加载器有自己的同步来处理类的静态初始化:保证在使用类之前所有静态初始化都已完成,并且在此代码中,类仅在 getInstance 中使用() 方法,所以那是当类加载时加载内部类。

顺便说一句,我期待着有一天会出现处理此类问题的 @Singleton 注释。

编辑:

一个特定的不信者声称包装类“什么都不做”。这证明它确实很重要,尽管是在特殊情况下。

与包装类版本的基本区别在于,在加载包装类时会创建单例实例,这在第一次调用 getInstance() 时会创建,但与非包装版本 - 即简单的静态初始化 - 加载主类时创建实例。

如果您只是简单地调用 getInstance() 方法,那么 几乎 没有区别 - 区别在于所有 other sttic 初始化会在之前在使用包装版本创建实例之前完成,但这很容易通过简单地在源代码中列出last的静态实例变量来解决。

但是,如果您按name 加载类,情况就完全不同了。在类上调用 Class.forName(className) 会导致发生静态初始化,因此如果要使用的单例类是服务器的属性,则使用简单版本时,将创建静态实例 Class.forName() 被调用,notgetInstance() 被调用。我承认这有点做作,因为您需要使用反射来获取实例,但是这里有一些完整的工作代码可以证明我的观点(以下每个类都是顶级类):

public abstract class BaseSingleton {
    private long createdAt = System.currentTimeMillis();

    public String toString() {
        return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago";
    }
}

public class EagerSingleton extends BaseSingleton {

    private static final EagerSingleton INSTANCE = new EagerSingleton();

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

public class LazySingleton extends BaseSingleton {
    private static class Loader {
        static final LazySingleton INSTANCE = new LazySingleton();
    }

    public static LazySingleton getInstance() {
        return Loader.INSTANCE;
    }
}

还有主要的:

public static void main(String[] args) throws Exception {
    // Load the class - assume the name comes from a system property etc
    Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
    Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.EagerSingleton");

    Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance()

    // Invoke the getInstace method on the class
    BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz);
    BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz);

    System.out.println(lazySingleton);
    System.out.println(eagerSingleton);
}

输出:

LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago

如您所见,未包装的简单实现是在调用 Class.forName() 时创建的,这可能是在静态初始化准备就绪之前被执行。

关于java - Java中的单例和多线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11072262/

相关文章:

c# - 如何为操作实现忽略/重试/取消模式?

java - 如何将编码图像转换为.PNG?

java - 比较 JTree 的有效方法是什么?

java - 世界上有更多的 body 时 FPS 速度较慢

python - 如何在 python 中正确关闭 gobject 主循环?

c# - 将 View 模型属性分组到不同的类别中是一种好的做法吗?

java - 如何为方法创建代理?

java - 用于远程 JVM 的 JVisualVM CPU 分析

在 for 循环内部或外部声明的 C++ 互斥锁

java - 排队多个下载,寻找生产者消费者 API