Reminder: Declaring an array as volatile does not ordinarily ensure
that the elements of the array maintain naively expected visibility
across threads.
This is one aspect of my rantings about the need for constructs to
obtain barriers. However, the following idiom, that requires no new
constructs, looks like it can be used to cover all cases of practical
interest. If so, I no longer think other constructs are needed.
If elements of a volatile array are always written under
synchronization on the array, then I think that they should always be
reliably readable without synch. For example:
class VolatileArray {
private final volatile int[] data;
public VolatileArray(int cap) { data = new int[cap]; }
public int get(int i) {
int[] d = data;
return d[i];
}
public void set(int i, int value) {
int[] d = data;
synchronized(d) { d[i] = value; }
}
}
The reasoning here is based on the observation that on any machine
that needs one, the get() method will first perform a read barrier at
"d = data", and so the d[i] returned is guaranteed be no staler than
the last committed write in set(). (As usual, on sparcs and pentiums,
a read barrier is not ordinarily needed here.) The synch on set()
ensures a write barrier. So things work out OK.
This adds a bit of overhead to method set() compared to a version of
this class that synchs on both set and get, since it requires an extra
read barrier (to read d and then synch on it) on machines that need
it. But even still, it should be a reasonably efficient way to allow
concurrent reads in the usual cases where these concerns apply -- when
reads are expected to vastly outnumber writes.
This construction does not directly apply in cases where you need to
perform coordinated updates to multiple array elements, or maintain
other invariants. But in these cases you couldn't allow concurrent
reads in the first place, at least not without otherwise modifying the
design.
Note that is is a case where both "final" and "volatile" sensibly
apply. (Although in this particular class, you could remove either one
of these qualifiers, assuming "final" works as it should.) In
addition to arrays, this construction also applies to references to
objects with nonvolatile fields. You can also extend it to apply to
non-final arrays by adding another level of indirection -- a volatile
forwarding pointer to the array reference.
I'm trying to convince myself that adding this construction to the set
of standard idioms suffices to cover the problematic cases I've been
discussing. So far I think it does -- I can think of no cases that
arise in which you could not tolerate the need to synchronize on
writes, and even pay this minor penalty to do so. (Although in most
cases, the basic idea has to be twisted in various ways to apply.) It
also seems possible to optimize these idioms within compilers so they
perform about as efficiently as the other proposals I've mentioned
that would require language extensions.
If you think this story makes sense, the remaining issue is that it
does not seem to fall out naturally within either the JLS or
Bill's memory models.
-Doug
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:23 EDT