Java 多线程单例
我已经读过很多关于在Java中为多线程环境创建单例的可能方法,Java 多线程单例,java,multithreading,Java,Multithreading,我已经读过很多关于在Java中为多线程环境创建单例的可能方法,,比如枚举、双重检查锁定等 我找到了一个简单的方法,也可以很好地工作,但我无法找到它的缺点或失败案例。请任何人解释一下什么时候可能会失败,或者为什么我们不应该选择这种方法 public final class MySingleton { public final static MySingleton INSTANCE = new MySingleton(); private MySingleton(){} } 我正
,比如枚举、双重检查锁定等
我找到了一个简单的方法,也可以很好地工作,但我无法找到它的缺点或失败案例。请任何人解释一下什么时候可能会失败,或者为什么我们不应该选择这种方法
public final class MySingleton {
public final static MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}
我正在使用以下代码对其进行测试,工作正常:
public class MyThread {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
MySingleton singleton = MySingleton.INSTANCE;
System.out.println(singleton.hashCode() + " " + Thread.currentThread().getName());
});
thread.start();
}
}
}
公共类读取{
公共静态void main(字符串[]args){
对于(int i=0;i<10000;i++){
线程=新线程(()->{
MySingleton=MySingleton.INSTANCE;
System.out.println(singleton.hashCode()+“”+Thread.currentThread().getName());
});
thread.start();
}
}
}
感谢您的每一条评论。您的单例在类加载时被实例化,因此当主方法启动时,它已经被实例化,并且从多线程的角度来看是安全的
但有一些观点认为这可能不是最好的方法:
- 当实例化你的单例抛出一个异常时,你会得到一个很难分析的令人讨厌的类加载异常,特别是当你没有一个调试器来附加的时候
- 当实例化需要一些时间时,为了最小化应用程序的启动时间,您可能不希望在加载类时执行此操作
此外,使用单例并不是一个好的设计,因为您需要硬编码客户机(使用该类)和该类实现之间的依赖关系。因此,很难用模拟来代替您的实现进行测试。它非常好而且简单。尽管您可以检查下面列出的缺点
可能导致资源浪费。因为类的实例总是被创建的,>无论它是否是必需的
若不需要,CPU时间也会浪费在实例的创建上
无法进行异常处理
您还必须确保MySingleton
类是线程安全的
看
是的,这是一个很好的单例实现
测试表明。。。某物但这并不能真正解释它是否起作用。您试图显示的内容(只创建了一个实例)基本上不可能通过测试来显示,因为这是语言规范所保证的
具体请参见,其中描述了类是如何初始化的
对于每个类或接口C,都有一个唯一的初始化锁LC。从C到LC的映射由Java虚拟机实现自行决定。初始化C的步骤如下:
同步C的初始化锁LC。这包括等待当前线程可以获取LC
如果C的类对象指示其他线程正在进行C的初始化,则释放LC并阻止当前线程,直到通知正在进行的初始化已完成,此时重复此步骤
如果C的类对象指示当前线程正在对C进行初始化,那么这必须是一个递归的初始化请求。释放信用证并正常完成
如果C的类对象指示C已经初始化,则无需进一步操作。释放信用证并正常完成
因此,类保证只初始化一次(如果有的话),因为初始化是在持有特定于类的锁的同时完成的;在保证获取和释放该锁之前发生的事件意味着最终字段的值是可见的;因此,INSTANCE
字段保证初始化一次,因此只有一个MySingleton
实例是可能的
请注意,您的实现实际上与枚举相同:
public enum MySingleton {
INSTANCE
}
枚举实际上只是类的语法糖。如果反编译枚举类,它将类似于:
public class MySingleton {
public static final MySingleton INSTANCE = new MySingleton(0);
private MySingleton(int ordinal) { ... }
}
啊,几个月前,我在使用这种方法时遇到了一些问题。这里的大多数人都会告诉你这很好,而且他们基本上是对的,只要你知道构造函数不会抛出异常。我将要描述的是JVM缓存约定的一个常见但不可预测的构件
我有一个单身汉,就是你在问题中提出的那个。每次尝试运行该程序时,我都会收到一个NoClassDefFoundError
。我不知道出了什么问题,直到我发现,恰当地命名为使用单例静态初始化是不好的
仅供参考,如果您使用静态初始化初始化一个单例,例如,私有静态最终MyObject instance=new MyObject();构造函数中有业务逻辑,您很容易遇到奇怪的元错误,比如NoClassDefFoundError。更糟糕的是,这是不可预测的。某些条件(如操作系统)可能会改变是否发生错误。静态初始化还包括使用静态块“Static{…}”,以及使用枚举单例模式
这篇文章还链接到了关于问题原因的信息
该解决方案对我来说非常有效,称为双重检查锁定
在大多数情况下,您可能可以用简单的方式完成,而不会遇到任何问题。但是,如果您的构造函数包含足够的业务逻辑,您开始得到NoClassDefFoundError
,那么这个稍微复杂一点的方法应该可以解决它。这确实很好:)其他模式通常会尝试帮助“仅在必要时加载”,但如果您对快速加载没有问题,那么这将满足您的需要。当我需要一个单例时,我通常也会这样做。“我正在用下面的代码测试它,工作正常”你想用这个测试显示什么?@AndyTurner我正在从多个单例打印hashcode值
public class Example {
private static volatile Example SINGLETON = null;
private static final Object LOCK = new Object();
private Example() {
// ...do stuff...
}
public static Example getInstance() {
if (SINGLETON == null) {
synchronized (LOCK) {
if (SINGLETON == null) {
SINGLETON = new Example();
}
}
}
return SINGLETON;
}
}