color Application-level concurrency:, Lifelock, Starvation

Summary of Yesterday’s lecture We learned about: • Learned how to define an optimistic strategy. • Discussed where such strategies are useful. • Learned about the compare-and-set (or compare-and-swap or CAS) and how to describe its operation using pseudo-code.

• Learned what the Atomic classes in Java’s atomic package do, where they can be used. • Talked about the ABA problem and seen examples of where it can arise. • Discussed the use of stamped references to address this problem.

Deadlocks

• Problematic in Java applications, because the only way to resolve a dead- lock is to terminate the application.

• Necessary and sufficient deadlock conditions (E. G. Coffman etal. System . Computing Surveys, Vol. 3, No. 2, June 1971)

– Mutual exclusion: Resources cannot be shared: One holds a resource that cannot be shared by another thread concurrently. – Hold and wait condition: There is a thread that holds a non-sharable resource and waits for another non-sharable resource that is held by another thread. – No-preemptive condition: Threads holding non-sharable resources have to give them up voluntarily - they cannot be forced from outside to release the resource. – Circular wait condition: Threads waiting for resources in a cyclic dependency.

Deadlock examples

• Dining Philosophers Problem, Drinking Philosophers Problem • Deadly embrace Deadlock.java

1 • Dynamic lock order deadlock. DeadlockingBankAccount.java

• Deadlock avoidance using lock hashes and tie braking locks to enforce ordering constraints.

Deadlocks The following is deadlock prone when executed concurrently: public class DeadlockProne{ Object lock1= new Object(); Object lock2= new Object(); public void transfer12(){ synchronized(lock1){ synchronized(lock2){ doSomeWork(); } } } public void transfer21(){ synchronized(lock2){ synchronized(lock1){ doSomeMoreWork(); } } } }

Deadlock avoidance private static final Object tieLock= new Object();

public void transferFunds(Account from, Account to, int amount){

class Helper{ public void transfer(){ from.debit(amount); to.credit(amount);} }

int fromHash= System.identityHashCode(from); int toHash= System.identityHashCode(to);

if(fromHash< toHash){ synchronized(fromHash){synchronized(toHash){new Helper.transfer();}} } else if(toHash< fromHash){ synchronized(toHash){synchronized(fromHash){new Helper.transfer();}} } else{ synchronized(tieLock){ synchronized(toHash){synchronized(fromHash){new Helper.transfer();}} } }

Livelock

• While not blocked, threads still cannot make progress.

2 • Example: Transactional messaging applications: – Transaction that cannot be processed successfully is rolled back and put back to waiting queue to be retried later. – Buggy error handler that treats this as a recoverable problem causes this loop not to terminate • Another possible livelock: Cooperating thread change state in response to others in a way that inhibits progress

LiveLockAtDinnertime.java

• Possible scenario: Threads try to access shared resource, detect collision, and try again after fixed time. • Possible solution: Introduce randomness.

Starvation

• Failure to acquire resources for extended times, potentially forever. • Most contended resource in practice: CPU cores • Potential issue with non-standard thread scheduling priorities

StarvingBankAccount.java

• JVMs do not make any guarantees about implementation of thread priori- ties: Could be dealt with by host OS, or ignored entirely, hence, behaviour is platform dependent • Advice: Do not fiddle with priorities without good reason! – A good reason to do so anyway could be: Background tasks cause GUI responsiveness issues. – Setting the priority below Thread.NORM PRIORITY may solve respon- siveness problem. Make sure there is no issue if this background tasks suffers from starvation!

Starvation Due to unfair locking: • Failure to acquire locks for extended times, potentially forever. • Lock acquisition may be highly unfair • Example: tryLock() in ReentrantLock has barging semantics: Lock is often acquired instantly, even if a large number of threads blocked on lock().

3 • Synchronized statements make no guarantees about fairness Wait set starvation: • Waiting on a monitor condition for extended times, potentially forever.

• Likely if using notify() instead of notifyAll() to signal. • There is no control over which thread is proceeding to lock acquisition, so there may be threads never leaving the condition queue.

Testing threaded applications Guidelines: • First test your code for correctness when executed sequentially, e.g., run only 1 thread at a time: Testing concurrency issues in isolation is hard enough! Concurrency strongly increases the number of possible execution paths. • Difficult to test: Infrequent race conditions or deadlocks. • Approaches to reveal these:

1. Test on different platforms and with different JVMs. 2. Create high contention, i.e., increase the number of threads as much as practical. 3. Things happen at context switches: Instrument code with Thread.yield() or Thread.sleep() methods at critical locations methods to encour- age problematic interference.

Summary Today we have discussed: • Deadlocks: static and dynamic lock ordering deadlocks • Livelock and Starvation

• Testing concurrent software

4