Race condition

  • A race condition occurs when two or more threads can access shared data and they try to change it at the same time, (both threads are "racing" to access/change the data.)

Problems often occur when one thread does a check-then-ac" (e.g. "check" if the value is X, then "act" to do something that depends on the value being X) and another thread does something to the value in between the "check" and the "act". E.g:

if (x == 5) { // The "Check"
   y = x * 2; // The "Act"

   // If another thread changed x in between
   // "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

The point being, y could be 10, or it could be anything, depending on whether another thread changed x in between the check and act. You have no real way of knowing.

In order to prevent race conditions from occurring, you would typically put a lock around the shared data to ensure only one thread can access the data at a time. This would mean something like this:

// Obtain lock for x
if (x == 5) {
   y = x * 2;
   // Now, nothing can change x until the lock is released.
   // Therefore y = 10
}
// release lock for x

Handling deadlock

Deadlock describes a situation where two or more threads are blocked forever, waiting for each other.

  • Deadlock occurs when multiple threads need the same locks but obtain them in different order.

  • A Java multithreaded program may suffer from the deadlock condition because the synchronized keyword causes the executing thread to block while waiting for the lock, or monitor, associated with the specified object.

Publication and data visibility

  • Writing a value to some variable from Thread A doesn't guarantee that the new value will be immediately visible from Thread B, or even visible at all.
  • Accessing multiple variables doesn't necessarily happen in "program order".
  • We need to explicitly deal with data visibility when data is to be accessed by multiple threads and/or when order of access is important, whether the access is concurrent or not.
    • For example, we need to take steps when:
      • an object or value is created by one thread then used by another (even where we don't expect the accesses to be concurrent);
      • one thread uses a variable such as a flag to signal to another thread;
      • we read from variable A, then write to variable B, and we strictly expect the first to "happen before" the second.
  • In general, correct synchronization solves the visibility problem, and many of the bugs that occur are when people think they've found a clever (but broken) way to avoid synchronization.

  • After Thread A exits a block synchronized on Object X, that Thread B when entering a block also synchronized on Object X will see the data as it was visible to thread A when it exited the block.

    • Implicit in this description is that the synchronized blocks guarantee ordering: data accesses from Thread A will strictly "happen before" data accesses in Thread B.
  • Java provides two other keywords, volatile and final, that in the right circumstances can be used to guarantee visibility:

    • if all of the fields on an object are final, then that object can be safely read from any thread without synchronization
    • if a variable is declared volatile, then this signals that the variable will be accessed by multiple threads, and also gives visibility guarantees

results matching ""

    No results matching ""