Re: Question whether a problem with race conditions exists in this case
All right, think a found a solution for my initial problem, which was
context switches bewtween lines 1,2,3:
public Object get() {
synchronized(lock) {
if(useNewValue.get()) // 1
return newValue; // 2
return previousValue; // 3
}
}
Solution for the new Node class looks like this (hope it works out
well with indentation and line breaks):
package test.switchover;
import java.util.concurrent.atomic.AtomicBoolean;
public class SwitchableValue
{
private Object lock = new Object();
private Object switchOverLock = null;
private Object newValue = null;
private Object previousValue = null;
private AtomicBoolean useNewValue = new AtomicBoolean(false);
public SwitchableValue() {
super();
}
public SwitchableValue(Object currentValue) {
super();
this.newValue = currentValue;
}
public void set(Object newValue, AtomicBoolean useNewValue, Object
switchOverLock) {
synchronized (lock) {
assert useNewValue.get() == false;
this.switchOverLock = switchOverLock;
this.useNewValue = useNewValue;
this.previousValue = this.newValue;
this.newValue = newValue;
}
}
public Object get() {
synchronized (lock) {
if(switchOverLock == null) {
// optimization to avoid 2 nested synchronized blocks
when
// not switching over to the new value, which is mostly
the case
return newValue;
}
synchronized (switchOverLock) {
// do the switch over, the if-then-else block should be
without
// any problems caused by context switches in between as
done
// from within the switchOverLock
if(useNewValue.get()) {
switchOverLock = null;
useNewValue = null;
return newValue;
}
return previousValue;
}
}
}
}
Here is some code to run a test case:
package test.switchover;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class SwitchOverTest {
public static void main(String[] args)
{
final int maxValues = 100;
List<Runnable> pollingRunnables = new ArrayList<Runnable>();
final List<SwitchableValue> values = new
ArrayList<SwitchableValue>();
final AtomicBoolean proceed = new AtomicBoolean(true);
for (int i = 0; i < maxValues; i++) {
final int j = i;
values.add(new SwitchableValue(new Integer(i)));
pollingRunnables.add(new Runnable() {
int pos = j;
public void run() {
while(proceed.get()) {
System.out.println("Value of runnable " + pos + ": " +
values.get(pos).get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
}
});
}
Iterator<Runnable> it = pollingRunnables.iterator();
while(it.hasNext()) {
new Thread(it.next()).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) { }
Object switchOverLock = new Object();
AtomicBoolean useNewValue = new AtomicBoolean(false);
for (int i = 0; i < maxValues; i++) {
values.get(i).set(i + 1, useNewValue, switchOverLock);
}
synchronized (switchOverLock) {
try {
System.out.println("Now all output to the console is suspended,
because the switchOverLock is held by the commit thread.");
System.out.println("Waiting for 4 seconds to be able to read the
message...");
Thread.sleep(4000);
System.out.println("All output to the console is resumed and the
values are all incremented by 1.");
System.out.println("Waiting for 4 seconds to be able to read the
message...");
Thread.sleep(4000);
} catch (InterruptedException e) { }
useNewValue.compareAndSet(false, true);
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) { }
System.out.println("stopping ...");
proceed.compareAndSet(true, false);
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
System.out.println("done.");
}
}
Cheers, Oliver