A set of methods providing fine-grained control over happens-before
and synchronization order relations among reads and/or writes. The
methods of this class are designed for use in uncommon situations
where declaring variables
volatile or
final, using
instances of atomic classes, using
synchronized blocks or
methods, or using other synchronization facilities are not possible
or do not provide the desired control.
Memory Ordering. There are three methods for controlling
ordering relations among memory accesses (i.e., reads and
writes). Method
orderWrites is typically used to enforce
order between two writes, and
orderAccesses between a write
and a read. Method
orderReads is used to enforce order
between two reads with respect to other
orderWrites and/or
orderAccesses invocations. The formally specified
properties of these methods described below provide
platform-independent guarantees that are honored by all levels of a
platform (compilers, systems, processors). The use of these
methods may result in the suppression of otherwise valid compiler
transformations and optimizations that could visibly violate the
specified orderings, and may or may not entail the use of
processor-level "memory barrier" instructions.
Each ordering method accepts a
ref argument, and
controls ordering among accesses with respect to this reference.
Invocations must be placed between accesses performed in
expression evaluations and assignment statements to control the
orderings of prior versus subsequent accesses appearing in program
order. These methods also return their arguments to simplify
correct usage in these contexts.
Usages of ordering methods almost always take one of the forms
illustrated in the examples below. These idioms arrange some of
the ordering properties associated with
volatile and
related language-based constructions, but without other
compile-time and runtime benefits that make language-based
constructions far better choices when they are applicable. Usages
should be restricted to the control of strictly internal
implementation matters inside a class or package, and must either
avoid or document any consequent violations of ordering or safety
properties expected by users of a class employing them.
Reachability. Method
reachabilityFenceestablishes an ordering for strong reachability (as defined in the
java.lang.ref package specification) with respect to
garbage collection. Method
reachabilityFence differs from
the others in that it controls relations that are otherwise only
implicit in a program -- the reachability conditions triggering
garbage collection. As illustrated in the sample usages below,
this method is applicable only when reclamation may have visible
effects, which is possible for objects with finalizers (see Section
12.6 of the Java Language Specification) that are implemented in
ways that rely on ordering control for correctness.
Sample Usages
Safe publication. With care, method
orderWritesmay be used to obtain the memory safety effects of
finalfor a field that cannot be declared as
final, because its
primary initialization cannot be performed in a constructor, in
turn because it is used in a framework requiring that all classes
have a no-argument constructor; as in:
class WidgetHolder {
private Widget widget;
public WidgetHolder() {}
public static WidgetHolder newWidgetHolder(Params params) {
WidgetHolder h = new WidgetHolder();
h.widget = new Widget(params);
return Fences.orderWrites(h);
}
}
Here, the invocation of
orderWrites ensures that the
effects of the widget assignment are ordered before those of any
(unknown) subsequent stores of
h in other variables that
make
h available for use by other objects. Initialization
sequences using
orderWrites require more care than those
involving
final fields. When
final is not used,
compilers cannot help you to ensure that the field is set correctly
across all usages. You must fully initialize objects
before the
orderWrites invocation that makes
references to them safe to assign to accessible variables. Further,
initialization sequences must not internally "leak" the reference
by using it as an argument to a callback method or adding it to a
static data structure. If less constrained usages were required,
it may be possible to cope using more extensive sets of fences, or
as a normally better choice, using synchronization (locking).
Conversely, if it were possible to do so, the best option would be
to rewrite class
WidgetHolder to use
final.
An alternative approach is to place similar mechanics in the
(sole) method that makes such objects available for use by others.
Here is a stripped-down example illustrating the essentials. In
practice, among other changes, you would use access methods instead
of a public field.
class AnotherWidgetHolder {
public Widget widget;
void publish(Widget w) {
this.widget = Fences.orderWrites(w);
}
// ...
}
In this case, the
orderWrites invocation occurs before the
store making the object available. Correctness again relies on
ensuring that there are no leaks prior to invoking this method, and
that it really is the
only means of accessing the
published object. This approach is not often applicable --
normally you would publish objects using a thread-safe collection
that itself guarantees the expected ordering relations. However, it
may come into play in the construction of such classes themselves.
Safely updating fields. Outside of the initialization
idioms illustrated above, Fence methods ordering writes must be
paired with those ordering reads. To illustrate, suppose class
c contains an accessible variable
data that should
have been declared as
volatile but wasn't:
class C {
Object data; // need volatile access but not volatile
// ...
}
class App {
Object getData(C c) {
return Fences.orderReads(c).data;
}
void setData(C c) {
Object newValue = ...;
c.data = Fences.orderWrites(newValue);
Fences.orderAccesses(c);
}
// ...
}
Method
getData provides an emulation of
volatilereads of (non-long/double) fields by ensuring that the read of
c obtained as an argument is ordered before subsequent
reads using this reference, and then performs the read of its
field. Method
setData provides an emulation of volatile
writes, ensuring that all other relevant writes have completed,
then performing the assignment, and then ensuring that the write is
ordered before any other access. These techniques may apply even
when fields are not directly accessible, in which case calls to
fence methods would surround calls to methods such as
c.getData(). However, these techniques cannot be applied to
long or
double fields because reads and writes of
fields of these types are not guaranteed to be
atomic. Additionally, correctness may require that all accesses of
such data use these kinds of wrapper methods, which you would need
to manually ensure.
More generally, Fence methods can be used in this way to achieve
the safety properties of
volatile. However their use does
not necessarily guarantee the full sequential consistency
properties specified in the Java Language Specification chapter 17
for programs using
volatile. In particular, emulation using
Fence methods is not guaranteed to maintain the property that
volatile operations performed by different threads are
observed in the same order by all observer threads.
Acquire/Release management of thread safe objects. It may
be possible to use weaker conventions for volatile-like variables
when they are used to keep track of objects that fully manage their
own thread-safety and synchronization. Here, an acquiring read
operation remains the same as a volatile-read, but a releasing
write differs by virtue of not itself ensuring an ordering of its
write with subsequent reads, because the required effects are
already ensured by the referenced objects.
For example:
class Item {
synchronized f(); // ALL methods are synchronized
// ...
}
class ItemHolder {
private Item item;
Item acquireItem() {
return Fences.orderReads(item);
}
void releaseItem(Item x) {
item = Fences.orderWrites(x);
}
// ...
}
Because this construction avoids use of
orderAccesses,
which is typically more costly than the other fence methods, it may
result in better performance than using
volatile or its
emulation. However, as is the case with most applications of fence
methods, correctness relies on the usage context -- here, the
thread safety of
Item, as well as the lack of need for full
volatile semantics inside this class itself. However, the second
concern means that it can be difficult to extend the
ItemHolder class in this example to be more useful.
Avoiding premature finalization. Finalization may occur
whenever a Java Virtual Machine detects that no reference to an
object will ever be stored in the heap: A garbage collector may
reclaim an object even if the fields of that object are still in
use, so long as the object has otherwise become unreachable. This
may have surprising and undesirable effects in cases such as the
following example in which the bookkeeping associated with a class
is managed through array indices. Here, method
actionuses a
reachabilityFence to ensure that the Resource
object is not reclaimed before bookkeeping on an associated
ExternalResource has been performed; in particular here, to ensure
that the array slot holding the ExternalResource is not nulled out
in method
Object#finalize, which may otherwise run
concurrently.
class Resource {
private static ExternalResource[] externalResourceArray = ...
int myIndex;
Resource(...) {
myIndex = ...
externalResourceArray[myIndex] = ...;
...
}
protected void finalize() {
externalResourceArray[myIndex] = null;
...
}
public void action() {
try {
// ...
int i = myIndex;
Resource.update(externalResourceArray[i]);
} finally {
Fences.reachabilityFence(this);
}
}
private static void update(ExternalResource ext) {
ext.status = ...;
}
}
Here, the call to
reachabilityFence is unintuitively
placed
after the call to
update, to ensure that
the array slot is not nulled out by
Object#finalize before
the update, even if the call to
action was the last use of
this object. This might be the case if for example a usage in a
user program had the form
new Resource().action(); which
retains no other reference to this Resource. While probably
overkill here,
reachabilityFence is placed in a
finally block to ensure that it is invoked across all paths in the
method. In a method with more complex control paths, you might
need further precautions to ensure that
reachabilityFenceis encountered along all of them.
It is sometimes possible to better encapsulate use of
reachabilityFence. Continuing the above example, if it
were OK for the call to method update to proceed even if the
finalizer had already executed (nulling out slot), then you could
localize use of
reachabilityFence:
public void action2() {
// ...
Resource.update(getExternalResource());
}
private ExternalResource getExternalResource() {
ExternalResource ext = externalResourceArray[myIndex];
Fences.reachabilityFence(this);
return ext;
}
Method
reachabilityFence is not required in
constructions that themselves ensure reachability. For example,
because objects that are locked cannot in general be reclaimed, it
would suffice if all accesses of the object, in all methods of
class Resource (including
finalize) were enclosed in
synchronized (this) blocks. (Further, such blocks must not include
infinite loops, or themselves be unreachable, which fall into the
corner case exceptions to the "in general" disclaimer.) However,
method
reachabilityFence remains a better option in cases
where this approach is not as efficient, desirable, or possible;
for example because it would encounter deadlock.
Formal Properties.
Using the terminology of The Java Language Specification chapter
17, the rules governing the semantics of the methods of this class
are as follows:
The following is still under construction.
[Definitions]
- Define sequenced(a, b) to be true if a
occurs before b in program order.
- Define accesses(a, p) to be true if
a is a read or write of a field (or if an array, an
element) of the object referenced by p.
- Define deeplyAccesses(a, p) to be true if either
accesses(a, p) or deeplyAccesses(a, q) where
q is the value seen by some read r
such that accesses(r, p).
[Matching]
Given:
- p, a reference to an object
- wf, an invocation of
orderWrites(p) or
orderAccesses(p)
- w, a write of value p
- rf, an invocation of
orderReads(p) or
orderAccesses(p)
- r, a read returning value p
If:
- sequenced(wf, w)
- read r sees write w
- sequenced(r, rf)
Then:
- wf happens-before rf
- wf precedes rf in the
synchronization order
- If (r1, w1) and (r2,
w2) are two pairs of reads and writes, both
respectively satisfying the above conditions for p,
and sequenced(r1, r2) then it is not the case that w2
happens-before w1.
[Initial Reads]
Given:
- p, a reference to an object
- a, an access where deeplyAccesses(a, p)
- wf, an invocation of
orderWrites(p) or
orderAccesses(p)
- w, a write of value p
- r, a read returning value p
- b, an access where accesses(b, p)
If:
- sequenced(a, wf);
- sequenced(wf, w)
- read r sees write w, and
r is the first read by some thread
t that sees value p
- sequenced(r, b)
Then:
- the effects of b are constrained
by the relation a happens-before b.
[orderAccesses]
Given:
- p, a reference to an object
- f, an invocation of
orderAccesses(p)
If:
Then:
- f is an element of the synchronization order.
[Reachability]
Given:
- p, a reference to an object
- f, an invocation of
reachabilityFence(p)
- a, an access where accesses(a, p)
- b, an action (by a garbage collector) taking
the form of an invocation of
p.finalize() or of enqueuing any
java.lang.ref.Reference constructed with argument p
If:
Then: