...
Code Block | ||
---|---|---|
| ||
class Flag { private boolean flag = true; public void toggle() { // unsafe flag = !flag; } public boolean getFlag() { // unsafe return flag; } } |
This solution clearly has a race condition It is prone to a data race because the value of flag
is read, negated, and written back.
Alternatively, the assignment operator ^=
can be used by the toggle()
method to negate the current value of flag
.
...
This solution is also not thread-safe. A data race condition exists because ^=
is a compound operation.
Consider, for example, two threads that call toggle()
. Theoretically, the effect of toggling flag
twice should restore it to its original value. But However, the following scenario could occur, leaving leaves flag
in the wrong state.:
Time | flagsflag= | Thread | Action |
---|---|---|---|
1 | true | t1 | reads the current value of |
2 | true | t2 | reads the current value of |
3 | true | t1 | toggles the temporary variable to false |
4 | true | t2 | toggles the temporary variable to false |
5 | false | t1 | writes the temporary variable's value to |
6 | false | t2 | writes the temporary variable's value to |
As a result, the effect of the call by t1 is not reflected in flag
; the program behaves as if the call was never made.
...
This guards reads and writes to the flag
field with a lock on the instance, that is, this
. This compliant solution ensures that changes are visible to all the threads. Now, only two execution orders are possible, one of which is shown below.
Time | flag= | Thread | Action |
---|---|---|---|
1 | true | t1 | reads the current value of |
2 | true | t1 | toggles the temporary variable to false |
3 | false | t1 | writes the temporary variable's value to |
4 | false | t2 | reads the current value of |
5 | false | t2 | toggles the temporary variable to true |
6 | true | t2 | writes the temporary variable's value to |
The second execution order involves the same operations, just that t2 starts and finishes before t1.
It is also permissible to declare flag
as volatile
to ensure its visibility and while doing so, forgoing to synchronize the getFlag()
method.
Code Block | ||
---|---|---|
| ||
class Flag {
private volatile boolean flag = true;
public synchronized void toggle() {
flag ^= true; // same as flag = !flag;
}
public boolean getFlag() {
return flag;
}
}
|
It is also permissible to declare flag
as volatile
to ensure its visibility and while doing so, forgoing to synchronize the getFlag()
method. The toggle()
method still requires synchronization because it performs a non-atomic operation. However, this advanced technique is brittle in most other scenarios, such as, when a getter method performs operations other than just returning the value of the volatile
field.
...