Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: added a table to CS, separated some advice into a code block, reworded a bit

...

Code Block
bgColor#FFcccc
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 flag, true, into a temporary variable

2

true

t2

reads the current value of flag, (still) true, into a temporary variable

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 flag

6

false

t2

writes the temporary variable's value to flag

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 flag, true, into a temporary variable

2

true

t1

toggles the temporary variable to false

3

false

t1

writes the temporary variable's value to flag

4

false

t2

reads the current value of flag, false, into a temporary variable

5

false

t2

toggles the temporary variable to true

6

true

t2

writes the temporary variable's value to flag

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
bgColor#ccccff

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.

...