Broken singleton without volatile example - java

I know, that in theory, to implement a correct singleton, in addition to double checked locking and synchronized we should make an instance field volatile.
But in real life I cannot get an example, that would expose the problem. Maybe there is a JVM flag that would disable some optimisation, or allow runtime to do such code permutation?
Here is the code, that, as I understand, should print to console from time to time, but it doesn't:
class Data {
int i;
Data() {
i = Math.abs(new Random().nextInt()) + 1; // Just not 0
}
}
class Keeper {
private Data data;
Data getData() {
if (data == null)
synchronized (this) {
if (data == null)
data = new Data();
}
return data;
}
}
#Test
void foo() throws InterruptedException {
Keeper[] sharedInstance = new Keeper[]{new Keeper()};
Thread t1 = new Thread(() -> {
while (true)
sharedInstance[0] = new Keeper();
});
t1.start();
final Thread t2 = new Thread(() -> {
while (true)
if (sharedInstance[0].getData().i == 0)
System.out.println("GOT IT!!"); // This actually does not happen
});
t2.start();
t1.join();
}
Could someone provide a code, that clearly demonstrates described theoretical lack of volatile problem?

Very good article about it
https://shipilev.net/blog/2014/safe-public-construction/
You can find examples in the end.
And be aware about
x86 is Total Store Order hardware, meaning the stores are visible for all processors in some total order. That is, if compiler actually presented the program stores in the same order to hardware, we may be reasonably sure the initializing stores of the instance fields would be visible before seeing the reference to the object itself. Even if your hardware is total-store-ordered, you can not be sure the compiler would not reorder within the allowed memory model spec. If you turn off -XX:+StressGCM -XX:+StressLCM in this experiment, all cases would appear correct since the compiler did not reorder much.

Related

Real world example of Memory Consistency Errors in multi-threading?

In the tutorial of java multi-threading, it gives an exmaple of Memory Consistency Errors. But I can not reproduce it. Is there any other method to simulate Memory Consistency Errors?
The example provided in the tutorial:
Suppose a simple int field is defined and initialized:
int counter = 0;
The counter field is shared between two threads, A and B. Suppose thread A increments counter:
counter++;
Then, shortly afterwards, thread B prints out counter:
System.out.println(counter);
If the two statements had been executed in the same thread, it would be safe to assume that the value printed out would be "1". But if the two statements are executed in separate threads, the value printed out might well be "0", because there's no guarantee that thread A's change to counter will be visible to thread B — unless the programmer has established a happens-before relationship between these two statements.
I answered a question a while ago about a bug in Java 5. Why doesn't volatile in java 5+ ensure visibility from another thread?
Given this piece of code:
public class Test {
volatile static private int a;
static private int b;
public static void main(String [] args) throws Exception {
for (int i = 0; i < 100; i++) {
new Thread() {
#Override
public void run() {
int tt = b; // makes the jvm cache the value of b
while (a==0) {
}
if (b == 0) {
System.out.println("error");
}
}
}.start();
}
b = 1;
a = 1;
}
}
The volatile store of a happens after the normal store of b. So when the thread runs and sees a != 0, because of the rules defined in the JMM, we must see b == 1.
The bug in the JRE allowed the thread to make it to the error line and was subsequently resolved. This definitely would fail if you don't have a defined as volatile.
This might reproduce the problem, at least on my computer, I can reproduce it after some loops.
Suppose you have a Counter class:
class Holder {
boolean flag = false;
long modifyTime = Long.MAX_VALUE;
}
Let thread_A set flag as true, and save the time into
modifyTime.
Let another thread, let's say thread_B, read the Counter's flag. If thread_B still get false even when it is later than modifyTime, then we can say we have reproduced the problem.
Example code
class Holder {
boolean flag = false;
long modifyTime = Long.MAX_VALUE;
}
public class App {
public static void main(String[] args) {
while (!test());
}
private static boolean test() {
final Holder holder = new Holder();
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(10);
holder.flag = true;
holder.modifyTime = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
long lastCheckStartTime = 0L;
long lastCheckFailTime = 0L;
while (true) {
lastCheckStartTime = System.currentTimeMillis();
if (holder.flag) {
break;
} else {
lastCheckFailTime = System.currentTimeMillis();
System.out.println(lastCheckFailTime);
}
}
if (lastCheckFailTime > holder.modifyTime
&& lastCheckStartTime > holder.modifyTime) {
System.out.println("last check fail time " + lastCheckFailTime);
System.out.println("modify time " + holder.modifyTime);
return true;
} else {
return false;
}
}
}
Result
last check time 1565285999497
modify time 1565285999494
This means thread_B get false from Counter's flag filed at time 1565285999497, even thread_A has set it as true at time 1565285999494(3 milli seconds ealier).
The example used is too bad to demonstrate the memory consistency issue. Making it work will require brittle reasoning and complicated coding. Yet you may not be able to see the results. Multi-threading issues occur due to unlucky timing. If someone wants to increase the chances of observing issue, we need to increase chances of unlucky timing.
Following program achieves it.
public class ConsistencyIssue {
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Increment(), "Thread-1");
Thread thread2 = new Thread(new Increment(), "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter);
}
private static class Increment implements Runnable{
#Override
public void run() {
for(int i = 1; i <= 10000; i++)
counter++;
}
}
}
Execution 1 output: 10963,
Execution 2 output: 14552
Final count should have been 20000, but it is less than that. Reason is count++ is multi step operation,
1. read count
2. increment count
3. store it
two threads may read say count 1 at once, increment it to 2. and write out 2. But if it was a serial execution it should have been 1++ -> 2++ -> 3.
We need a way to make all 3 steps atomic. i.e to be executed by only one thread at a time.
Solution 1: Synchronized
Surround the increment with Synchronized. Since counter is static variable you need to use class level synchronization
#Override
public void run() {
for (int i = 1; i <= 10000; i++)
synchronized (ConsistencyIssue.class) {
counter++;
}
}
Now it outputs: 20000
Solution 2: AtomicInteger
public class ConsistencyIssue {
static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Increment(), "Thread-1");
Thread thread2 = new Thread(new Increment(), "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.get());
}
private static class Increment implements Runnable {
#Override
public void run() {
for (int i = 1; i <= 10000; i++)
counter.incrementAndGet();
}
}
}
We can do with semaphores, explicit locking too. but for this simple code AtomicInteger is enough
Sometimes when I try to reproduce some real concurrency problems, I use the debugger.
Make a breakpoint on the print and a breakpoint on the increment and run the whole thing.
Releasing the breakpoints in different sequences gives different results.
Maybe to simple but it worked for me.
Please have another look at how the example is introduced in your source.
The key to avoiding memory consistency errors is understanding the happens-before relationship. This relationship is simply a guarantee that memory writes by one specific statement are visible to another specific statement. To see this, consider the following example.
This example illustrates the fact that multi-threading is not deterministic, in the sense that you get no guarantee about the order in which operations of different threads will be executed, which might result in different observations across several runs. But it does not illustrate a memory consistency error!
To understand what a memory consistency error is, you need to first get an insight about memory consistency. The simplest model of memory consistency has been introduced by Lamport in 1979. Here is the original definition.
The result of any execution is the same as if the operations of all the processes were executed in some sequential order and the operations of each individual process appear in this sequence in the order specified by its program
Now, consider this example multi-threaded program, please have a look at this image from a more recent research paper about sequential consistency. It illustrates what a real memory consistency error might look like.
To finally answer your question, please note the following points:
A memory consistency error always depends on the underlying memory model (A particular programming languages may allow more behaviours for optimization purposes). What's the best memory model is still an open research question.
The example given above gives an example of sequential consistency violation, but there is no guarantee that you can observe it with your favorite programming language, for two reasons: it depends on the programming language exact memory model, and due to undeterminism, you have no way to force a particular incorrect execution.
Memory models are a wide topic. To get more information, you can for example have a look at Torsten Hoefler and Markus Püschel course at ETH Zürich, from which I understood most of these concepts.
Sources
Leslie Lamport. How to Make a Multiprocessor Computer That Correctly Executes Multiprocessor Programs, 1979
Wei-Yu Chen, Arvind Krishnamurthy, Katherine Yelick, Polynomial-Time Algorithms for Enforcing Sequential Consistency in SPMD Programs with Arrays, 2003
Design of Parallel and High-Performance Computing course, ETH Zürich

Replace synchronized with atomic+while loop in case of low lock contention

I have two functions which must run in a critical section:
public synchronized void f1() { ... }
public synchronized void f2() { ... }
Assume that the behavior is as following:
f1 is almost never called. Actually, under normal conditions, this method is never called. If f1 is called anyway, it should return quickly.
f2 is called at a very high rate. It returns very quickly.
These methods never call each other and there is no reentrancy as well.
In other words, there is very low contention. So when f2 is called, we have some overhead to acquire the lock, which is granted immediately in 99,9% of the cases. I am wondering if there are approaches to avoid this overhead.
I came up with the following alternative:
private final AtomicInteger lock = new AtomicInteger(0);
public void f1() {
while (!lock.compareAndSet(0, 1)) {}
try {
...
} finally {
lock.set(0);
}
}
public void f2() {
while (!lock.compareAndSet(0, 2)) {}
try {
...
} finally {
lock.set(0);
}
}
Are there other approaches? Does the java.util.concurrent package offer something natively?
update
Although my intention is to have a generic question, some information regarding my situation:
f1: This method creates a new remote stream, if for some reason the current one becomes corrupt, for example due to a timeout. A remote stream could be considered as a socket connection which consumes a remote queue starting from a given location:
private Stream stream;
public synchronized void f1() {
final Stream stream = new Stream(...);
if (this.stream != null) {
stream.setPosition(this.stream.getPosition());
}
this.stream = stream;
return stream;
}
f2: This method advances the stream position. It is a plain setter:
public synchronized void f2(Long p) {
stream.setPosition(p);
}
Here, stream.setPosition(Long) is implemented as a plain setter as well:
public class Stream {
private volatile Long position = 0;
public void setPosition(Long position) {
this.position = position;
}
}
In Stream, the current position will be sent to the server periodically asynchronously. Note that Stream is not implemented by myself.
My idea was to introduce compare-and-swap as illustrated above, and mark stream as volatile.
Your example isn't doing what you want it to. You are actually executing your code when the lock is being used. Try something like this:
public void f1() {
while (!lock.compareAndSet(0, 1)) {
}
try {
...
} finally {
lock.set(0);
}
}
To answer your question, I don't believe that this will be any faster than using synchronized methods, and this method is harder to read and comprehend.
From the description and your example code, I've inferred the following:
Stream has its own internal position, and you're also tracking the most recent position externally. You use this as a sort of 'resume point': when you need to reinitialize the stream, you advance it to this point.
The last known position may be stale; I'm assuming this based on your assertion that the stream periodically does asynchronously notifies the server of its current position.
At the time f1 is called, the stream is known to be in a bad state.
The functions f1 and f2 access the same data, and may run concurrently. However, neither f1 nor f2 will ever run concurrently against itself. In other words, you almost have a single-threaded program, except for the rare cases when both f1 and f2 are executing.
[Side note: My solution doesn't actually care if f1 gets called concurrently with itself; it only cares that f2 is not called concurrently with itself]
If any of this is wrong, then the solution below is wrong. Heck, it might be wrong anyway, either because of some detail left out, or because I made a mistake. Writing low-lock code is hard, which is exactly why you should avoid it unless you've observed an actual performance issue.
static class Stream {
private long position = 0L;
void setPosition(long position) {
this.position = position;
}
}
final static class StreamInfo {
final Stream stream = new Stream();
volatile long resumePosition = -1;
final void setPosition(final long position) {
stream.setPosition(position);
resumePosition = position;
}
}
private final Object updateLock = new Object();
private final AtomicReference<StreamInfo> currentInfo = new AtomicReference<>(new StreamInfo());
void f1() {
synchronized (updateLock) {
final StreamInfo oldInfo = currentInfo.getAndSet(null);
final StreamInfo newInfo = new StreamInfo();
if (oldInfo != null && oldInfo.resumePosition > 0L) {
newInfo.setPosition(oldInfo.resumePosition);
}
// Only `f2` can modify `currentInfo`, so update it last.
currentInfo.set(newInfo);
// The `f2` thread might be waiting for us, so wake them up.
updateLock.notifyAll();
}
}
void f2(final long newPosition) {
while (true) {
final StreamInfo s = acquireStream();
s.setPosition(newPosition);
s.resumePosition = newPosition;
// Make sure the stream wasn't replaced while we worked.
// If it was, run again with the new stream.
if (acquireStream() == s) {
break;
}
}
}
private StreamInfo acquireStream() {
// Optimistic concurrency: hope we get a stream that's ready to go.
// If we fail, branch off into a slower code path that waits for it.
final StreamInfo s = currentInfo.get();
return s != null ? s : acquireStreamSlow();
}
private StreamInfo acquireStreamSlow() {
synchronized (updateLock) {
while (true) {
final StreamInfo s = currentInfo.get();
if (s != null) {
return s;
}
try {
updateLock.wait();
}
catch (final InterruptedException ignored) {
}
}
}
}
If the stream has faulted and is being replaced by f1, it is possible that an earlier call to f2 is still performing some operations on the (now defunct) stream. I'm assuming this is okay, and that it won't introduce undesirable side effects (beyond those already present in your lock-based version). I make this assumption because we've already established in the list above that your resume point may be stale, and we also established that f1 is only called once the stream is known to be in a bad state.
Based on my JMH benchmarks, this approach is around 3x faster than the CAS or synchronized versions (which are pretty close themselves).
Another approach is to use a timestamp lock which works like a modification count. This works well if you have a high read to write ratio.
Another approach is to have an immutable object which stores state via an AtomicReference. This works well if you have a very high read to write ratio.

WeakReference not collected in curly brackets?

This fails
public void testWeak() throws Exception {
waitGC();
{
Sequence a = Sequence.valueOf("123456789");
assert Sequence.used() == 1;
a.toString();
}
waitGC();
}
private void waitGC() throws InterruptedException {
Runtime.getRuntime().gc();
short count = 0;
while (count < 100 && Sequence.used() > 0) {
Thread.sleep(10);
count++;
}
assert Sequence.used() == 0: "Not removed!";
}
The test fails. Telling Not removed!.
This works:
public void testAWeak() throws Exception {
waitGC();
extracted();
waitGC();
}
private void extracted() throws ChecksumException {
Sequence a = Sequence.valueOf("123456789");
assert Sequence.used() == 1;
a.toString();
}
private void waitGC() throws InterruptedException {
Runtime.getRuntime().gc();
short count = 0;
while (count < 100 && Sequence.used() > 0) {
Thread.sleep(10);
count++;
}
assert Sequence.used() == 0: "Not removed!";
}
It seems like the curly brackets does not affect the weakness.
Some official resources?
Scope is a compile-time thing. It is not determining the reachability of objects at runtime, only has an indirect influence due to implementation details.
Consider the following variation of your test:
static boolean WARMUP;
public void testWeak1() throws Exception {
variant1();
WARMUP = true;
for(int i=0; i<10000; i++) variant1();
WARMUP = false;
variant1();
}
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
if(!WARMUP) System.out.println("variant1: "
+(waitGC(track)? "collected": "not collected"));
}
public void testWeak2() throws Exception {
variant2();
WARMUP = true;
for(int i=0; i<10000; i++) variant2();
WARMUP = false;
variant2();
}
private void variant2() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
if(!WARMUP) System.out.println("variant2: "
+(waitGC(track)? "collected": "not collected"));
}
}
static class Trackable {
final AtomicBoolean backRef;
public Trackable(AtomicBoolean backRef) {
this.backRef = backRef;
}
#Override
protected void finalize() throws Throwable {
backRef.set(true);
}
}
private boolean waitGC(AtomicBoolean b) throws InterruptedException {
for(int count = 0; count < 10 && !b.get(); count++) {
Runtime.getRuntime().gc();
Thread.sleep(1);
}
return b.get();
}
on my machine, it prints:
variant1: not collected
variant1: collected
variant2: not collected
variant2: collected
If you can’t reproduce it, you may have to raise the number of warmup iterations.
What it demonstrates: whether a is in scope (variant 2) or not (variant 1) doesn’t matter, in either case, the object has not been collected in cold execution, but got collected after a number of warmup iterations, in other words, after the optimizer kicked in.
Formally, a is always eligible for garbage collection at the point we’re invoking waitGC(), as it is unused from this point. This is how reachability is defined:
A reachable object is any object that can be accessed in any potential continuing computation from any live thread.
In this example, the object can not be accessed by potential continuing computation, as no such subsequent computation that would access the object exists. However, there is no guaranty that a particular JVM’s garbage collector is always capable of identifying all of those objects at each time. In fact, even a JVM not having a garbage collector at all would still comply to the specification, though perhaps not the intent.
The possibility of code optimizations having an effect on the reachability analysis has also explicitly mentioned in the specification:
Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.
So what happens technically?
As said, scope is a compile-time thing. At the bytecode level, leaving the scope defined by the curly braces has no effect. The variable a is out of scope, but its storage within the stack frame still exists holding the reference until overwritten by another variable or until the method completes. The compiler is free to reuse the storage for another variable, but in this example, no such variable exists. So the two variants of the example above actually generate identical bytecode.
In an unoptimized execution, the still existing reference within the stack frame is treated like a reference preventing the object’s collection. In an optimized execution, the reference is only held until its last actual use. Inlining of its fields can allow its collection even earlier, up to the point that it is collected right after construction (or not getting constructed at all, if it hadn’t a finalize() method). The extreme end is finalize() called on strongly reachable object in Java 8…
Things change, when you insert another variable, e.g.
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
String message = "variant1: ";
if(!WARMUP) System.out.println(message
+(waitGC(track)? "collected": "not collected"));
}
Then, the storage of a is reused by message after a’s scope ended (that’s of course, compiler specific) and the object gets collected, even in the unoptimized execution.
Note that the crucial aspect is the actual overwriting of the storage. If you use
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
if(!WARMUP)
{
String message = "variant1: "
+(waitGC(track)? "collected": "not collected");
System.out.println(message);
}
}
The message variable uses the same storage as a, but its assignment only happens after the invocation of waitGC(track), so you get the same unoptimized execution behavior as in the original variant.
By the way, don’t use short for local loop variables. Java always uses int for byte, short, char, and int calculations (as you know, e.g. when trying to write shortVariable = shortVariable + 1;) and requiring it to cut the result value to short (which still happens implicitly when you use shortVariable++), adds an additional operation, so if you thought, using short improved the efficiency, notice that it actually is the opposite.

How to solve race condition of two writers using immutable objects

I was thinking about how to solve race condition between two threads which tries to write to the same variable using immutable objects and without helping any keywords such as synchronize(lock)/volatile in java.
But I couldn't figure it out, is it possible to solve this problem with such solution at all?
public class Test {
private static IAmSoImmutable iAmSoImmutable;
private static final Runnable increment1000Times = () -> {
for (int i = 0; i < 1000; i++) {
iAmSoImmutable.increment();
}
};
public static void main(String... args) throws Exception {
for (int i = 0; i < 10; i++) {
iAmSoImmutable = new IAmSoImmutable(0);
Thread t1 = new Thread(increment1000Times);
Thread t2 = new Thread(increment1000Times);
t1.start();
t2.start();
t1.join();
t2.join();
// Prints a different result every time -- why? :
System.out.println(iAmSoImmutable.value);
}
}
public static class IAmSoImmutable {
private int value;
public IAmSoImmutable(int value) {
this.value = value;
}
public IAmSoImmutable increment() {
return new IAmSoImmutable(++value);
}
}
If you run this code you'll get different answers every time, which mean a race condition is happening.
You can not solve race condition without using any of existence synchronisation (or volatile) techniques. That what they were designed for. If it would be possible there would be no need of them.
More particularly your code seems to be broken. This method:
public IAmSoImmutable increment() {
return new IAmSoImmutable(++value);
}
is nonsense for two reasons:
1) It makes broken immutability of class, because it changes object's variable value.
2) Its result - new instance of class IAmSoImmutable - is never used.
The fundamental problem here is that you've misunderstood what "immutability" means.
"Immutability" means — no writes. Values are created, but are never modified.
Immutability ensures that there are no race conditions, because race conditions are always caused by writes: either two threads performing writes that aren't consistent with each other, or one thread performing writes and another thread performing reads that give inconsistent results, or similar.
(Caveat: even an immutable object is effectively mutable during construction — Java creates the object, then populates its fields — so in addition to being immutable in general, you need to use the final keyword appropriately and take care with what you do in the constructor. But, those are minor details.)
With that understanding, we can go back to your initial sentence:
I was thinking about how to solve race condition between two threads which tries to write to the same variable using immutable objects and without helping any keywords such as synchronize(lock)/volatile in java.
The problem here is that you actually aren't using immutable objects: your entire goal is to perform writes, and the entire concept of immutability is that no writes happen. These are not compatible.
That said, immutability certainly has its place. You can have immutable IAmSoImmutable objects, with the only writes being that you swap these objects out for each other. That helps simplify the problem, by reducing the scope of writes that you have to worry about: there's only one kind of write. But even that one kind of write will require synchronization.
The best approach here is probably to use an AtomicReference<IAmSoImmutable>. This provides a non-blocking way to swap out your IAmSoImmutable-s, while guaranteeing that no write gets silently dropped.
(In fact, in the special case that your value is just an integer, the JDK provides AtomicInteger that handles the necessary compare-and-swap loops and so on for threadsafe incrementation.)
Even if the problems are resolved by :
Avoiding the change of IAmSoImmutable.value
Reassigning the new object created within increment() back into the iAmSoImmutable reference.
There still are pieces of your code that are not atomic and that needs a sort of synchronization.
A solution would be to use a synchronized method of course
public synchronized static void increment() {
iAmSoImmutable = iAmSoImmutable.increment();
}
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});

Brian Goetz's improper publication

The question has been posted before but no real example was provided that works. So Brian mentions that under certain conditions the AssertionError can occur in the following code:
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");
}
}
When holder is improperly published like this:
class someClass {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
I understand that this would occur when the reference to holder is made visible before the instance variable of the object holder is made visible to another thread. So I made the following example to provoke this behavior and thus the AssertionError with the following class:
public class Publish {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
public static void main(String[] args) {
Publish publish = new Publish();
Thread t1 = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < Integer.MAX_VALUE; i++) {
publish.initialize();
}
System.out.println("initialize thread finished");
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
int nullPointerHits = 0;
int assertionErrors = 0;
while(t1.isAlive()) {
try {
publish.holder.assertSanity();
} catch(NullPointerException exc) {
nullPointerHits++;
} catch(AssertionError err) {
assertionErrors ++;
}
}
System.out.println("Nullpointerhits: " + nullPointerHits);
System.out.println("Assertion errors: " + assertionErrors);
}
});
t1.start();
t2.start();
}
}
No matter how many times I run the code, the AssertionError never occurs. So for me there are several options:
The jvm implementation (in my case Oracle's 1.8.0.20) enforces that the invariants set during construction of an object are visible to all threads.
The book is wrong, which I would doubt as the author is Brian Goetz ... nuf said
I'm doing something wrong in my code above
So the questions I have:
- Did someone ever provoke this kind of AssertionError successfully? With what code then?
- Why isn't my code provoking the AssertionError?
Your program is not properly synchronized, as that term is defined by the Java Memory Model.
That does not, however, mean that any particular run will exhibit the assertion failure you are looking for, nor that you necessarily can expect ever to see that failure. It may be that your particular VM just happens to handle that particular program in a way that turns out never to expose that synchronization failure. Or it may turn out the although susceptible to failure, the likelihood is remote.
And no, your test does not provide any justification for writing code that fails to be properly synchronized in this particular way. You cannot generalize from these observations.
You are looking for a very rare condition. Even if the code reads an unintialized n, it may read the same default value on the next read so the race you are looking for requires an update right in between these two adjacent reads.
The problem is that every optimizer will coerce the two reads in your code into one, once it starts processing your code, so after that you will never get an AssertionError even if that single read evaluates to the default value.
Further, since the access to Publish.holder is unsynchronized, the optimizer is allowed to read its value exactly once and assume unchanged during all subsequent iterations. So an optimized second thread would always process the same object which will never turn back to the uninitialized state. Even worse, an optimistic optimizer may go as far as to assume that n is always 42 as you never initialize it to something else in this runtime and it will not consider the case that you want a race condition. So both loops may get optimized to no-ops.
In other words: if your code doesn’t fail on the first access, the likeliness of spotting the error in subsequent iterations dramatically drops down, possibly to zero. This is the opposite of your idea to let the code run inside a long loop hoping that you will eventually encounter the error.
The best chances for getting a data race are on the first, non-optimized, interpreted execution of your code. But keep in mind, the chance for that specific data race are still extremely low, even when running the entire test code in pure interpreted mode.

Categories