两种单例实现同样多线程证明?
Both Singleton implementations equally Multi Threading proof?
下面的代码片段中有两个单例实现。
它们都是在 Java 中的多线程环境中实现单例的正确方法吗?
如果是这样,是不是说明第二种实现方式就完全没用了,想实现单例,就用enum方式吧?
我写单例的首选方式:
public enum Singleton {
INSTANCE();
}
另一种写单例的方式(显式同步):
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instanz == null) {
instanz = new Singleton();
}
return instanz;
}
两者都是线程安全的,但第一个使用枚举可避免不必要的锁定,在大多数情况下更可取。
你们这两种方法看起来都不错。但是,对于创建 Singleton,我建议使用以下方法,它是 100% 线程安全的并且没有同步问题,因为 它利用了 Java 的 class 加载机制。
P.S.: 感谢 @james 触发。
懒惰的单例:
像这样 class Provider 只会被加载一次,因此只有一个 Network class 实例将存在,并且没有两个线程可以在同一个 JVM 中创建 2 个实例,因为 class 不会被加载两次.
优点:
- 仅在需要时创建实例。
- 调用
getInstance()
时无需承担同步开销,当有getInstance()
同步时才会发生
class中还可以有其他静态方法可以不创建实例调用,只在需要调用实例方法时才创建实例。这是惰性单例的一个很好的优势。
public class Network {
private Network(){
}
private static class Provider {
static final Network INSTANCE = new Network();
}
public static Network getInstance() {
return Provider.INSTANCE;
}
//More code...
}
渴望单例:
private static Network INSTANCE = null;
private Network(){
}
static {
INSTANCE = new Network();
}
public static Network getInstance() {
return INSTANCE;
}
最后:你说得对,你指定的第二种方法确实是最后的选择,因为它必须承担每次 2 个线程尝试时的同步成本获取单例实例。
现在,它的 ENUM 单例游戏 v/s 懒惰的单例。我已经为您提供了几个链接,说明为什么人们不喜欢 ENUM 作为单例,以及 ENUM 单例的可扩展性 v/s 基于 class 加载的惰性单例。
这两种方法都可以无缝工作,所以它的选择问题,因此正确的答案是 "It depends!!!" :)
第一个解决方案适用于创建单例,因为您想要对某事物的单个实例建模。在此解决方案中,如果在创建实例期间发生异常,例如未找到文件,您将获得 classnotfound 异常。
第二种解决方案适用于创建单例时,因为对象的创建需要很长时间,并且涉及到对文件系统或网络的访问。
一些程序员之所以讨论如何创建单例的方式,是因为他们希望支持惰性创建,但担心访问器方法的后续调用的性能。
因此您的 class 使用 synchronized
实现了惰性创建方面,但后续调用可能会受到当时不必要的 synchronized
开销的影响。为了克服这个假设的开销,发明了臭名昭著的双重检查锁定,这引起了更多关于这个实际上完全不相关的“问题”的讨论:
如果你想将惰性创建与单例结合起来,你可以使用:
public class Singleton {
static final Singleton INSTANCE=new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return INSTANCE;
}
}
整个构造是惰性的,因为 class 初始化不会在第一个线程调用 getInstance()
之前发生并且它是线程安全的,因为 class 初始化本身保证JVM 线程安全。这是最有效的解决方案,因为后续调用将只读取 final
字段而无需同步。
这正是使用
时会发生的情况
public enum Singleton {
INSTANCE;
}
它使您无需声明 private
构造函数,因为编译器会为您做这些。并且该字段隐式为 static final
。一个区别是字段本身是公开的,不需要访问器方法。
此外,enum
s 本质上支持序列化。存储常量时,不会存储实例数据,只会存储类型和常量名称。在反序列化时,将查找当前运行时实例。所以如果你需要序列化支持,enum
应该是首选,否则没有太大区别(使用 enum
可以节省一些输入)。
但是
- 您的应用程序多久需要一次单例? (如果你说“经常”,你应该重新考虑你的软件设计)
- 与您的应用程序相关的单例访问器方法的性能多久一次?
- 单例实例创建的惰性有多频繁?
- 如果您需要单例,那么您真正需要关于单例的“铁一般”保证的频率是多少?属性?即,如果有人设法使用肮脏的反射技巧创建第二个实例,那么可能出现的问题是他的问题,不是吗?
简单地说,Singleton Design Pattern 似乎被严重高估了,关于如何有效实现它的讨论甚至更多……
下面的代码片段中有两个单例实现。 它们都是在 Java 中的多线程环境中实现单例的正确方法吗?
如果是这样,是不是说明第二种实现方式就完全没用了,想实现单例,就用enum方式吧?
我写单例的首选方式:
public enum Singleton {
INSTANCE();
}
另一种写单例的方式(显式同步):
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instanz == null) {
instanz = new Singleton();
}
return instanz;
}
两者都是线程安全的,但第一个使用枚举可避免不必要的锁定,在大多数情况下更可取。
你们这两种方法看起来都不错。但是,对于创建 Singleton,我建议使用以下方法,它是 100% 线程安全的并且没有同步问题,因为 它利用了 Java 的 class 加载机制。
P.S.: 感谢 @james 触发。
懒惰的单例:
像这样 class Provider 只会被加载一次,因此只有一个 Network class 实例将存在,并且没有两个线程可以在同一个 JVM 中创建 2 个实例,因为 class 不会被加载两次.
优点:
- 仅在需要时创建实例。
- 调用
getInstance()
时无需承担同步开销,当有getInstance()
同步时才会发生 class中还可以有其他静态方法可以不创建实例调用,只在需要调用实例方法时才创建实例。这是惰性单例的一个很好的优势。
public class Network { private Network(){ } private static class Provider { static final Network INSTANCE = new Network(); } public static Network getInstance() { return Provider.INSTANCE; } //More code...
}
渴望单例:
private static Network INSTANCE = null;
private Network(){
}
static {
INSTANCE = new Network();
}
public static Network getInstance() {
return INSTANCE;
}
最后:你说得对,你指定的第二种方法确实是最后的选择,因为它必须承担每次 2 个线程尝试时的同步成本获取单例实例。
现在,它的 ENUM 单例游戏 v/s 懒惰的单例。我已经为您提供了几个链接,说明为什么人们不喜欢 ENUM 作为单例,以及 ENUM 单例的可扩展性 v/s 基于 class 加载的惰性单例。
这两种方法都可以无缝工作,所以它的选择问题,因此正确的答案是 "It depends!!!" :)
第一个解决方案适用于创建单例,因为您想要对某事物的单个实例建模。在此解决方案中,如果在创建实例期间发生异常,例如未找到文件,您将获得 classnotfound 异常。
第二种解决方案适用于创建单例时,因为对象的创建需要很长时间,并且涉及到对文件系统或网络的访问。
一些程序员之所以讨论如何创建单例的方式,是因为他们希望支持惰性创建,但担心访问器方法的后续调用的性能。
因此您的 class 使用 synchronized
实现了惰性创建方面,但后续调用可能会受到当时不必要的 synchronized
开销的影响。为了克服这个假设的开销,发明了臭名昭著的双重检查锁定,这引起了更多关于这个实际上完全不相关的“问题”的讨论:
如果你想将惰性创建与单例结合起来,你可以使用:
public class Singleton {
static final Singleton INSTANCE=new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return INSTANCE;
}
}
整个构造是惰性的,因为 class 初始化不会在第一个线程调用 getInstance()
之前发生并且它是线程安全的,因为 class 初始化本身保证JVM 线程安全。这是最有效的解决方案,因为后续调用将只读取 final
字段而无需同步。
这正是使用
时会发生的情况public enum Singleton {
INSTANCE;
}
它使您无需声明 private
构造函数,因为编译器会为您做这些。并且该字段隐式为 static final
。一个区别是字段本身是公开的,不需要访问器方法。
此外,enum
s 本质上支持序列化。存储常量时,不会存储实例数据,只会存储类型和常量名称。在反序列化时,将查找当前运行时实例。所以如果你需要序列化支持,enum
应该是首选,否则没有太大区别(使用 enum
可以节省一些输入)。
但是
- 您的应用程序多久需要一次单例? (如果你说“经常”,你应该重新考虑你的软件设计)
- 与您的应用程序相关的单例访问器方法的性能多久一次?
- 单例实例创建的惰性有多频繁?
- 如果您需要单例,那么您真正需要关于单例的“铁一般”保证的频率是多少?属性?即,如果有人设法使用肮脏的反射技巧创建第二个实例,那么可能出现的问题是他的问题,不是吗?
简单地说,Singleton Design Pattern 似乎被严重高估了,关于如何有效实现它的讨论甚至更多……