安全发布和安全初始化

https://shipilev.net/blog/2014/safe-public-construction/

Theory
Singletons and Singleton Factories
It is mildly irritating when people confuse Singletons, and Singleton Factories. For the sake of our discussion, we need to clearly separate these two notions. Singleton is an object that has only a single instance at every point of program life-cycle. Singleton Factory is an object that maintains Singletons. Of course, you can conflate these two in a single implementation, but that is not the point of this post.

A reasonable SingletonFactory has a few properties:

It provides the public API for getting a Singleton instance.

It is thread-safe. No matter how many threads are requesting a Singleton, all threads will get the same Singleton instance, regardless of the current state.

It is lazy. One can argue about this, but non-lazy factories are not interesting for our discussion. Singleton initialization should happen with the first request for a Singleton, not when Singleton class is initialized. If no one wants a Singleton instance, it should not be instantiated.

It is efficient. The overheads for managing a Singleton state should be kept at minimum.

One can be reasonably sure this is not a good SingletonFactory:

======================================================
public class UnsafeDCLFactory {
private Singleton instance;

public Singleton get() {
if (instance == null) { // read 1, check 1
synchronized (this) {
if (instance == null) { // read 2, check 2
instance = new Singleton();
}
}
}
return instance; // read 3
}
}

Alas, this construction does not work properly for two reasons.

One could think that after “check 1” had succeeded, the Singleton instance is properly initialized, and we can return it. That is not correct: the Singleton contents are only fully visible to the constructing thread! There are no guarantees that you will see Singleton contents properly in other threads, because you are racing against the initializer thread. Once again, even if you have observed the non-null instance, it does not mean you observe its internal state properly. In JMM-ese speak, there is no happens-before between the initializing stores in Singleton constructor, and the reads of Singleton fields.

Notice that we do several reads of instance in this code, and at least “read 1” and “read 3” are the reads without any synchronization — that is, those reads are racy. One of the intents of the Java Memory Model is to allow reorderings for ordinary reads, otherwise the performance costs would be prohibitive. Specification-wise, as mentioned in happens-before consistency rules, a read action can observe the unordered write via the race. This is decided for each read action, regardless what other actions have already read the same location. In our example, that means that even though “read 1” could read non-null instance, the code then moves on to returning it, then it does another racy read, and it can read a null instance, which would be returned!

==========================================================
Safe Publication
Now we are going to describe the concept of safe publication. Safe publication differs from a regular publication on one crucial point. Safe publication makes all the values written before the publication visible to all readers that observed the published object. It is a great simplification over the JMM rules of engagement with regards to actions, orders and such.

There are a few trivial ways to achieve safe publication:

Exchange the reference through a properly locked field (JLS 17.4.5)

Use static initializer to do the initializing stores (JLS 12.4)

Exchange the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes

Initialize the value into a final field (JLS 17.5).

Let us try to exploit each of those ways. The most trivial example is publishing through a properly locked field:

public class SynchronizedCLFactory {
private Singleton instance;

public Singleton get() {
synchronized(this) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}

This is the most trivial example spec-wise:

1、Mutual exclusion during get() calls allow only a single thread to do the Singleton initialization.

2、The lock acquisition and releasing yield the synchronization actions that are bound in synchronizes-with, and then with happens-before. This forces the threads acquiring the lock to see the result of all the actions that were done before the lock release.

Classic holder idiom does roughly the same, but piggy-backs on class initialization locks. It safely publishes the object by doing the initialization in the static initializer. Note this thing is lazy, since we do not initialize Holder until the first call to get():

public class HolderFactory {
public static Singleton get() {
return Holder.instance;
}

private static class Holder {
public static final Singleton instance = new Singleton();
}
}
This is how it works spec-wise:

Class initialization is done under the lock, as per JLS 12.4.2. Class initialization lock provides the mutual exclusion during the class initialization, that is, only a single thread can initialize the static fields.

The release of class initialization lock plays the necessary role in establishing the happens-before relationships between the actions in static initializers and any users of the static fields. Naively speaking, the propagation of memory effects requires any reader of static field to acquire the class initialization lock first, but JLS allows to elide that locking, if the memory effects are still maintained. Indeed, modern VMs do this optimization routinely.

Now, get back to the infamous volatile DCL, that works because now we safely publish the singleton via volatile:

。。。。。。。。。。。。。。。。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值