java单例实例化_java – 单例实例化

原因是线程安全。

你所熟悉的形式有可能将单例初始化很多次。而且,即使在多次初始化之后,不同线程的getInstance()调用也可能会返回不同的实例!此外,一个线程可能会看到部分初始化的单例实例! (假设构造函数连接到一个DB并进行身份验证;一个线程可能在认证发生之前可以获得对单例的引用,即使它在构造函数中完成了!)

处理线程时有一些困难:

>并发性:它们必须具有同时执行的潜力;

>可见性:对一个线程所做的内存的修改对其他线程可能不可见;

>重新排序:无法预测代码执行的顺序,这可能会导致非常奇怪的结果。

您应该研究这些困难,以明确了解这些奇怪的行为在JVM中是完全合法的,为什么他们真的很好,以及如何保护他们。

静态块由JVM保证仅被执行一次(除非您使用不同的ClassLoaders加载和初始化类,但是细节超出了这个问题的范围,我会说),只有一个线程,并且其结果保证对每个其他线程都是可见的。

这就是为什么你应该在静态块上初始化单例。

我的首选模式:线程安全和懒惰

上面的模式将在第一次实例化单例时执行看到对Map_en_US类的引用(实际上,只有对类本身的引用将加载它,但是可能还没有初始化它;更多的细节,请查看引用)。也许你不想要也许你希望只有在第一次调用Map_en_US.getInstance()时才能初始化单例(就像你所熟悉的你所熟悉的模式一样)。

如果这是你想要的,你可以使用以下模式:

public class Singleton {

private Singleton() { ... }

private static class SingletonHolder {

private static final Singleton instance = new Singleton();

}

public static Singleton getInstance() {

return SingletonHolder.instance;

}

}

在上面的代码中,单例将只在初始化SingletonHolder类时被实例化。这只会发生一次(除非我以前说过,你使用多个ClassLoaders),代码只能由一个线程执行,结果将没有可见性问题,初始化只会在第一个引用SingletonHolder ,这发生在getInstance()方法中。这是当我需要一个单身人士时最常用的模式。

另一种模式…

同步getInstace()

正如在这个答案的评论中所讨论的,还有一种以线程安全的方式来实现单例,并且与你熟悉的(破碎的)几乎相同:

public class Singleton {

private static Singleton instance;

public static synchronized getInstance() {

if (instance == null)

instance = new Singleton();

}

}

上述代码由内存模型保证是线程安全的。 JVM规范说明如下(以更加隐秘的方式):让L成为任何对象的锁,让T1和T2成为两个线程。在T1发生L之前,T2发生L。

这意味着在释放锁之前由T1完成的每一件事情在每个其他线程获得相同的锁之后将是可见的。

所以,假设T1是输入getInstance()方法的第一个线程。直到它完成,没有其他线程将能够输入相同的方法(因为它是同步的)。它会看到该实例为null,将实例化一个Singleton并将其存储在字段中。然后它将释放锁并返回实例。

然后,等待锁的T2将能够获取并输入该方法。由于它获得了T1刚刚发布的相同的锁,T2将会看到字段实例包含与T1创建的完全相同的Singleton实例,并且将简单地返回它。更重要的是,由T1完成的单例初始化发生在T2锁定之前发生的锁的释放之前,T2在T2锁定之前发生,所以没有办法T2可以看到部分初始化的单例。

上面的代码是完全正确的。唯一的问题是对单例的访问将被序列化。如果发生很多,它将降低应用程序的可扩展性。这就是为什么我更喜欢上面显示的SingletonHolder模式:访问单例将是真正的并发的,而不需要同步!

双重锁定(DCL)

通常人们对锁的收购成本感到害怕。我已经看到,现在这与大多数应用程序并不相关。锁获取的真正问题是通过将访问序列化到同步块来损害可扩展性。

有人设计了一种巧妙的方法来避免获取锁,它被称为双重锁定。问题是大多数实现都是坏的。也就是说,大多数实现不是线程安全的(即,与原始问题上的getInstace()方法一样线程不安全)。

实现DCL的正确方法如下:

public class Singleton {

private static volatile Singleton instance;

public static Singleton getInstance() {

if (instance == null) {

synchronized {

if (instance == null) {

instance = new Singleton();

}

}

}

return instance;

}

}

这个正确的和不正确的实现之间的区别是volatile关键字。

要理解为什么,让T1和T2是两个线程。我们首先假设该领域不是挥发性的。

T1进入getInstace()方法。这是第一个进入它,所以字段是空的。然后它进入同步块,然后进入第二个if。它也评估为true,所以T1创建一个新的单例实例并将其存储在字段中。然后释放锁,并返回单例。对于这个线程,可以保证Singleton被完全初始化。

现在,T2进入getInstace()方法。它可能(虽然不保证)它会看到该实例!= null。然后它将跳过if块(所以它永远不会获取锁),并将直接返回Singleton的实例。由于重新排序,T2可能不会在其构造函数中看到Singleton执行的所有初始化!重新审视数据库连接单例,T2可能会看到一个连接但尚未认证的单例!

了解更多信息…

…我会推荐一本精彩的书,Java并发实践,以及Java语言规范。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值