3.5. Safe Publication(安全的公开)

3.5. Safe Publication(安全的公开)
So far we have focused on ensuring that an object not be published, such as when it is supposed to be confined to a thread or within another object. Of course, sometimes we do want to share objects across threads, and in this case we must do so safely. Unfortunately, simply storing a reference to an object into a public field, as in Listing 3.14, is not enough to publish that object safely.
到目前为止,我们一直聚焦于保证一个对象不要公开,例如对象是否应该局限在线程中或者在另外一个线程的内部。当然,有时候会不得不在线程之间共享线程,这种情况下,我们就必须不得不安全的做这件事情。不幸的是,简单的将对一个对象的应用保存为public域,对保证对象的安全性而言是不够的。
Listing 3.13. Caching the Last Result Using a Volatile Reference to an Immutable Holder Object.
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache =
new OneValueCache(null, null);

public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(resp, factors);
}
}

Listing 3.14. Publishing an Object without Adequate Synchronization. Don't Do this.

// Unsafe publication
public Holder holder;

public void initialize() {
holder = new Holder(42);
}

You may be surprised at how badly this harmless-looking example could fail. Because of visibility problems, the Holder could appear to another thread to be in an inconsistent state, even though its invariants were properly established by its constructor! This improper publication could allow another thread to observe a partially constructed object.
你可能会对看起来如此良好的的例子居然会失败而感到惊讶。因为可见性的原因,即使被良好的构造,Holder对象也可能在不同的线程中出现不一致的状态。不恰当的公开可能会使得其他线程看到部分被构建的对象。
3.5.1. Improper Publication: When Good Objects Go Bad
You cannot rely on the integrity of partially constructed objects. An observing thread could see the object in an inconsistent state, and then later see its state suddenly change, even though it has not been modified since publication. In fact, if the Holder in Listing 3.15 is published using the unsafe publication idiom in Listing 3.14, and a thread other than the publishing thread were to call assertSanity, it could throw AssertionError![15]
[15] The problem here is not the Holder class itself, but that the Holder is not properly published. However, Holder can be made immune to improper publication by declaring the n field to be final, which would make Holder immutable; see Section 3.5.2.
你不能够指望部分构造对象的完整性。观察者线程可能会看到对象处于不一致的状态中,然后看到该对象的状态被突然改变。尽管在该对象被构造后,其状态从来没有被改变。事实上,如果Listing3.15中的Holder类使用不正确的公开语法进行公开的话,当其他线程访问asserSanity方法的时候,可能会得到AssertionError异常。
这里的问题并不是Holder类本身,而是Holder类并没有被恰当的公开。通过将变量声明称final类型,Holder类可以避免不恰当的公开,这样做将会使得Holder类变成Immutable。
Listing 3.15. Class at Risk of Failure if Not Properly Published.

public class Holder {
private int n;

public Holder(int n) { this.n = n; }

public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}

Because synchronization was not used to make the Holder visible to other threads, we say the Holder was not properly published. Two things can go wrong with improperly published objects. Other threads could see a stale value for the holder field, and thus see a null reference or other older value even though a value has been placed in holder. But far worse, other threads could see an up-todate value for the holder reference, but stale values for the state of the Holder.[16] To make things even less predictable, a thread may see a stale value the first time it reads a field and then a more up-to-date value the next time, which is why assertSanity can throw AssertionError.
由于在使得Holder变得对其他线程可见的时候并没有使用同步机制,我们认为Holder类没有恰当的公开。不恰当的公开可能会导致两种错误发生,别的线程可能会看到holder域的过期值,比如看到null引用或者其它比较旧的数据,即便是数据有可能已经被赋过值。更为糟糕的是,其他线程可能会看到holder应用的最新值,以及Holder状态的过期值。这会使得程序的行为更加不可预测。线程可能会在第一次的时候看到一个过期值,然后在下次看到一个新值,这就是为什么assertSanity可能会抛出AssertError异常。
[16] While it may seem that field values set in a constructor are the first values written to those fields and therefore that there are no "older" values to see as stale values, the Object constructor first writes the default values to all fields before subclass constructors run. It is therefore possible to see the default value for a field as a stale value.
看起来好像域值在构造器中第一次被赋值,所以不存在过期值,实际上是构造器Object的构造器会在子构造器运行之前首先将所有域都附上一个默认值,这就是我们会在这个类中看到过期值。
At the risk of repeating ourselves, some very strange things can happen when data is shared across threads without sufficient synchronization.
由于自我复制的风险的存在,如果没有充分的同步机制,当数据在线程间共享的时候,有可能会发生很多奇怪的事情。
3.5.2. Immutable Objects and Initialization Safety(Immutable对象和安全初始化)
Because immutable objects are so important, the Java Memory Model offers a special guarantee of initialization safety for sharing immutable objects. As we've seen, that an object reference becomes visible to another thread does not necessarily mean that the state of that object is visible to the consuming thread. In order to guarantee a consistent view of the object's state, synchronization is needed.
由于Immutable对象是如此的重要,JMM特地为共享Immutable对象的初始化提供了特别的保护机制。就像我们已将看到的那样,一个对象的应用对其他线程变成可见,并不是必然意味着对象的状态对消费线程是可见的,为了保证一个对象状态的一致视图,同步机制是必须的。
Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the object reference. For this guarantee of initialization safety to hold, all of the requirements for immutability must be met: unmodi-fiable state, all fields are final, and proper construction. (If Holder in Listing 3.15 were immutable, assertSanity could not throw AssertionError, even if the Holder was not properly published.)
Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.
Immutable对象,即使在synchronization没有被使用的情况下,也可以被任何线程安全的使用。
但是,即使在没有使用到同步机制的情况下,Immutable对象也可以安全的公开对象应用。为了保障安全初始化,所有的immutability的需求必须被满足:无法修改的状态,所有的域都是final的,正确的构建方式。如果Listing3.5中的Holder是Immutable的话,即使在Holder没有被正确公开的情况下,asserSanity也不会抛出AssertionError异常。
This guarantee extends to the values of all final fields of properly constructed objects; final fields can be safely accessed without additional synchronization. However, if final fields refer to mutable objects, synchronization is still required to access the state of the objects they refer to.
这种机制还会扩展到被正确构建对象的所有final域:final域可以不需要额外的synchronization而且安全的访问。但是如果final域指向一个mutable对象,synchronization仍然是必须的。
3.5.3. Safe Publication Idioms(安全公开)
Objects that are not immutable must be safely published, which usually entails synchronization by both the publishing and the consuming thread. For the moment, let's focus on ensuring that the consuming thread can see the object in its as published state; we'll deal with visibility of modifications made after publication soon.
非Immutable的对象必须被安全的公开,这通常需要publish线程和comsume线程都必须使用synchronization。在这种情况下,我们应该聚焦于如何确保consume线程可以看到对象的公开状态,并且我们需要处理在Immutable对象被修改之后,其修改后状态的可见性问题。
To publish an object safely, both the reference to the object and the object's state must be made visible to other threads at the same time. A properly constructed object can be safely published by:
• Initializing an object reference from a static initializer;
• Storing a reference to it into a volatile field or AtomicReference;
• Storing a reference to it into a final field of a properly constructed object; or
• Storing a reference to it into a field that is properly guarded by a lock.
为了安全的公开对象,对对象的引用以及对象的状态必须同时被其他线程变得可见。一个被正确构建的对象可以通过如下方式被正确的公开:
从静态代码块中初始化对象的应用。
将应用存入volatile域或者AtomicReference中。
将应用存入一个被安全构建的对象的final域中。
将应用存入一个被锁机制恰当守护的域中。
The internal synchronization in thread-safe collections means that placing an object in a thread-safe collection, such as a Vector or synchronized List, fulfills the last of these requirements. If thread A places object X in a thread-safe collection and thread B subsequently retrieves it, B is guaranteed to see the state of X as A left it, even though the application code that hands X off in this manner has no explicit synchronization. The thread-safe library collections offer the following safe publication guarantees, even if the Javadoc is less than clear on the subject:
线程安全的容器中内部的同步机制意味着将一个对象放入诸如Vector或者同步List里面满足了以上规范的最后一条需求。如果线程A将对象X放入到线程安全的容器中,线程B随后想要访问该对象,这样就可以保证线程B看到A离开时对象X的状态。即使处理对象X的应用代码在没有显式同步机制的情况下,这点依然可以得到保障。尽管Javadoc没有明确的列出,线程安全的容器库提供了下列的安全公开保证:
• Placing a key or value in a Hashtable, synchronizedMap, or Concurrent-Map safely publishes it to any thread that retrieves it from the Map (whether directly or via an iterator);
• 将健或者值放入Hashtable,synchronizedMap或者并发Map中,可以保证对其他访问线程的安全公开。(无论是通过iterator或者直接访问)
• Placing an element in a Vector, CopyOnWriteArrayList, CopyOnWrite-ArraySet, synchronizedList, or synchronizedSet safely publishes it to any thread that retrieves it from the collection;
• 将一个元素放入Vector、CopyOnWriteArrayList, CopyOnWrite-ArraySet, synchronizedList, or synchronizedSet中后,可以确保任何线程都可以安全的访问这些元素。
• Placing an element on a BlockingQueue or a ConcurrentLinkedQueue safely publishes it to any thread that retrieves it from the queue.
• 将一个元素放入BlockingQueue 或者ConcurrentLinkedQueue可以确保任何线程都可以安全的访问这些元素。
Other handoff mechanisms in the class library (such as Future and Exchanger) also constitute safe publication; we will identify these as providing safe publication as they are introduced.
类库中的其他的一些切换机制(例如Future和Exchanger)也可以确保安全的公开。我们将在介绍到这些类的时候,提供对安全公开性的描述。
Using a static initializer is often the easiest and safest way to publish objects that can be statically constructed:
public static Holder holder = new Holder(42);
使用静态初始化通常是最简单最安全的公开对象的方法。
Static initializers are executed by the JVM at class initialization time; because of internal synchronization in the JVM, this mechanism is guaranteed to safely publish any objects initialized in this way [JLS 12.4.2].
静态初始化时JVM在类初始化的时候执行的,由于JVM内在的同步机制,可以确保对象的安全公开。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值