在Java中实现单例设计模式的有效方法是什么?
当前回答
这是如何实现一个简单的单例:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
这是如何正确地延迟创建单例:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
其他回答
枚举单例
实现线程安全的单例的最简单方法是使用Enum:
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
自从在Java 1.5中引入Enum以来,此代码一直有效
双重检查锁定
如果你想编写一个在多线程环境中工作的“经典”单例(从Java1.5开始),你应该使用这个。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这在1.5之前不是线程安全的,因为volatile关键字的实现不同。
早期加载单例(甚至在Java1.5之前就可以使用)
这个实现在加载类时实例化单例,并提供线程安全性。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
if (INSTANCE != null)
throw new IllegalStateException(“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
由于我们在getInstance之前添加了Synchronized关键字,因此在两个线程同时调用getInstance的情况下,我们避免了竞争条件。
免责声明:我刚刚总结了所有很棒的答案,并用自己的话写了出来。
在实施Singleton时,我们有两种选择:
延迟加载早期加载
延迟加载增加了位开销(老实说,很多),所以只有当您有非常大的对象或大量的构造代码,并且有其他可访问的静态方法或字段(可能在需要实例之前使用)时,才需要使用延迟初始化。否则,选择提前加载是一个不错的选择。
实现单例的最简单方法是:
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
一切都很好,除了它是一个早期加载的单例。让我们尝试延迟加载的单例
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
到目前为止还不错,但我们的英雄在与多个邪恶线程单独战斗时将无法生存,这些线程需要我们英雄的许多实例。因此,让我们保护它免受邪恶的多线程攻击:
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
但这还不足以保护英雄,真的!!!这是我们能/应该做的最好的事情来帮助我们的英雄:
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
这被称为“双重检查锁定习惯用法”。人们很容易忘记不稳定的说法,也很难理解为什么有必要这样做。详细信息:“双重检查锁定失败”声明
现在我们确定了邪恶的线索,但残酷的连载呢?我们必须确保即使在反序列化时也不会创建新对象:
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// The rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
方法readResolve()将确保返回唯一的实例,即使对象在我们的程序的上一次运行中被序列化。
最后,我们添加了足够的线程和序列化保护,但我们的代码看起来又大又难看。让我们给我们的英雄一个新形象:
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
是的,这是我们的英雄:)
由于行private static final Foo INSTANCE=new Foo();仅在实际使用FooLoader类时执行,这会处理延迟实例化,并保证线程安全。
我们已经走到了今天。以下是实现我们所做一切的最佳方式:
public enum Foo {
INSTANCE;
}
内部将被视为
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
就是这样!不再担心序列化、线程和丑陋的代码。ENUMS单例也被延迟初始化。
该方法在功能上等同于公共领域方法,除了它更简洁,提供了序列化机制免费提供,并提供多重保险实例化,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,单个元素枚举类型是实现单例的最佳方式。
-《有效的Java》中的约书亚·布洛赫
现在您可能已经意识到为什么ENUMS被认为是实现单例的最佳方式,并感谢您的耐心:)
在我的博客上更新了它。
创建单例对象的各种方法:
根据约书亚·布洛赫的说法,埃努姆将是最好的。也可以使用双重检查锁定。甚至可以使用内部静态类。
在写它之前,真的要考虑一下为什么你需要一个单例。关于使用它们有一个准宗教的争论,如果你在Java中搜索单例,你很容易就会发现。
就我个人而言,出于许多原因,我尽量避免单身汉,其中大部分可以通过谷歌搜索单身汉找到。我觉得单身汉经常被虐待,因为他们很容易被每个人理解。它们被用作将“全局”数据引入OO设计的机制,因为它很容易绕过对象生命周期管理(或者真正思考如何从B内部实现a)。看看像控制反转(IoC)或依赖注入(DI)这样的事情,可以找到一个不错的中间地带。
如果你真的需要一个,那么Wikipedia有一个很好的例子来说明单例的正确实现。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap