Doug Lea suggested I summarize this thread for those who were too swamped to
pay attention the first time around. Here goes:
David Smiley <dsmiley@mitre.org> asks:
> What I know is to try not to avoid synchronization. But,
> there must be /some/ techniques that are okay to do.
>
> Two that come to mind are:
>
> (1) A latch. If you've got a boolean in a class called
> something like "initialized" that begins being false and
> is eventually true forever... I assume that another thread
> will /eventually/ see that it is initialized in a reasonably
> small amount of time even though there is no explicit
> memory barrier.
>
> As for the latch style problem what about this latch scenario:
>
class Span{
volatile boolean init= false; /* eventually true permanently */
int start= 0, end= 0; /* constraint: b >= a */
/* assumed to be called only once ever */
void doInit(int a, int b) {
if (b < a)
throw IllegalArgumentException();
start= a; end=b;
init= true;
}
/* -1 if not ready, the span's length otherwise */
int getLength() {
return (init) ? start-end : -1;
}
}
1A. The code above is correct according to the proposed models.
Summary: The key is that 'init' is declared volatile. Without this, the
thread calling getLength may never see "init == true" or it may see "init ==
true" before it sees the initialized values for 'a' and 'b'.
1B. There was also some discussion about: When, if ever, normal
(unsynchronized, nonvolatile) memory operations are guaranteed to complete.
Summary: Normal memory operations must complete before the executing thread
releases a lock or writes a volatile variable. The effects of these memory
operations will be visible to another thread after this second thread
acquires the same lock or reads the same volatile variable.
1C. There was also some discussion about: When locks can be removed.
Summary: Locks that are only used by one thread can be removed. This
implies that locks on thread-local objects can always be removed. However,
locks cannot be removed simply because the lock region is empty.
> (2) Copy on write. Again, I assume that another thread
> will /eventually/ see the new reference to whatever was
> replaced in a reasonably small amount of time even
> though there is no explicit memory barrier.
>
For example:
class Singleton {
static Foo FOO;
}
Thread 1:
Foo foo = new Foo();
Singleton.FOO = foo;
Thread 2:
Foo foo = Singleton.FOO;
Summary: The code above is incorrect. Without synchronization or volatile,
thread 2 may never see the new foo object. (See 1B above for more about
when if ever thread 2 will see the new foo object.)
Furthermore, without synchronization or volatile, even if thread 2 does see
the reference to the new foo object, it is not guaranteed to see the
initialized contents of foo (the effects of foo's constructor) *unless* all
the fields of Foo are declared final and/or its accessors are synchronized.
To correct this, both threads should synchronize or Singleton.FOO should be
declared volatile. Note: In both proposed models, volatile has been
strengthened sufficiently so that it can be used with object references, as
well as primitive types.
-- Joe BowbeerPS - Brian Goetz does a good job answering the many questions raised by his DCL-is-broken article in JavaWorld:
http://www.javaworld.com/javaworld/jw-03-2001/jw-0323-letters.html
------------------------------- JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:31 EDT