Skip to the content.
    
    
      Thread Synchronization
-
What we will cover this lecture
	- Race Conditions
 
	- Preventing race conditions
  	
  	     - explicit mutual exclusion locking
        
        
 
        - implicit monitor locking
            
                - `Synchronized` signature clause
 
                - `Synchronous` blocks
 
            
         
    
     - Deadlocks
 
</ul>
-
-
## Race Conditions
* Situation in which multiple threads have access to the same object and try to modify that object without checking if it's used by other threads.
* Can cause `ConcurrentModificationException` to be thrown
-
#### Defining a Runnable Class
* Consider an application in which, multiple threads are responsible for mutating a single object-state across multiple `Thread` objects.
```java
public class AsynchCounter implements Runnable {
    private int count = 0;
    public void run() {
        for(int i = 0; i < 1_000_000; i++) {
            count = count + 1;
        }
        String currentThread = Thread.currentThread().toString();
        System.out.println("THREAD FINISHED: " + currentThread);
    }
}
```
-
#### Using Runnable to Construct Thread
```java
public class MainApplication {
    public static void main(String[] args) {
        AsynchCounter runnable = new AsynchCounter();
        Thread first = new Thread(runnable);
        Thread second = new Thread(runnable);
        first.start();
        second.start();
        ThreadUtils.sleep(5000); // sleep 5 seconds
        System.out.println(runnable.getCount());
    }
}
```
Output
```
THREAD FINISHED: Thread[Thread-0,5,main]
THREAD FINISHED: Thread[Thread-1,5,main]
1131532
```
-
#### Identifying Race conditions
* Notice that the output of the previous program was a value greater than 1,000,000.
* This is caused by threads accessing _stale_ data.
  * Data is considered _stale_ when the value of an operand changes but fetching the operand obtains the old, rather than the new, value of the operand
-
-
## Preventing Race Conditions
-
-
#### ReentrantLock Overview
* Also known as _mutual exclusion Lock_
* `java.util.concurrent.locks.ReentrantLock` is a built-in java-class that provides synchronization to methods while accessing shared resources.
-
#### ReentrantLock Tidbits
* are owned by the thread last successfully locking, but not yet unlocking it.
* thread invoking lock will return when lock is not owned by another thread.
* method will return immediately if current thread already owns the lock.
-
-
### Creating Reentrant Structures
* Consider an application in which, multiple threads are responsible for mutating a single object-state across multiple `Thread` objects.
```java
public class ReentrantCounter implements Runnable {
    private int count = 0;
    private ReentrantLock lock;
    public ReentrantCounter(ReentrantLock lock) {this.lock = lock;}
    public void run() {
        lock.lock(); // thread resources locked
        for(int i = 0; i < 1_000_000; i++) {
            count = count + 1;
        }
        String currentThread = Thread.currentThread().toString();
        System.out.println("THREAD FINISHED: " + currentThread);
        lock.unlock();  // thread resources unlocked
    }
}
```
-
#### Using Reentrant Structures
* Notice the predictable output of `demo`
```java
public void demo() {
    ReentrantLock lock = new ReentrantLock();
    ReentrantCounter runnable = new ReentrantCounter(lock);
    Thread first = new Thread(runnable);
    Thread second = new Thread(runnable);
    first.start();
    second.start();
    ThreadUtils.sleep(5000); // sleep 5 seconds
    System.out.println(runnable.getCount());
}
```
Output
```
THREAD FINISHED: Thread[Thread-0,5,main]
THREAD FINISHED: Thread[Thread-1,5,main]
2000000
```
-
-
### Creating Reentrant Structures
* Consider the case that `ReentrantCounter` never calls `unlock` on its `ReentrantLock` field.
```java
public class ReentrantCounter implements Runnable {
    private int count = 0;
    private ReentrantLock lock;
    public ReentrantCounter(ReentrantLock lock) {this.lock = lock;}
    public void run() {
        lock.lock(); // locked
        for(int i = 0; i < 1_000_000; i++) {
            count = count + 1;
        }
        String currentThread = Thread.currentThread().toString();
        System.out.println("THREAD FINISHED: " + currentThread);
        // never unlocked
    }
}
```
-
#### Using Reentrant Structures
* Notice the output of `demo`
```java
public void demo() {
    ReentrantLock lock = new ReentrantLock();
    ReentrantCounter runnable = new ReentrantCounter(lock);
    Thread first = new Thread(runnable);
    Thread second = new Thread(runnable);
    first.start();
    second.start();
    ThreadUtils.sleep(5000); // sleep 5 seconds
    System.out.println(runnable.getCount());
}
```
Output
```
THREAD FINISHED: Thread[Thread-0,5,main]
THREAD FINISHED: Thread[Thread-1,5,main]
1000000
```
-
-
## Synchronized Clause Overview
* `synchronized` is declared in the _signature_ of a synchronous method.
* allows a method to be _tagged_ as a synchronous operation
  * this enforces that a method is wrapped by an implicit `ReentrantLock.lock()` and `ReentrantLock.unlock()` method call.
-
-
### Synchronized Clause Declaration
Instance method declaration
```java
public synchronized Integer doComputation() {
  // definition omitted
}
```
Static method declaration
```java
public static synchronized Integer doComputation() {
  // definition omitted
}
```
-
### Creating Synchronized Methods
* Consider an application in which, multiple threads are responsible for mutating a single object-state across multiple `Thread` objects.
```java
public class SynchronizedCounterr implements Runnable {
    private int count = 0;
    public void run() {
        for(int i = 0; i < 1_000_000; i++) { incrementCount(); }
        String currentThread = Thread.currentThread().toString();
        System.out.println("THREAD FINISHED: " + currentThread);
    }
    public synchronized void incrementCount() { this.count+=1; }
    public int getCount() { return count; }
}
```
-
#### Using Synchronized Methods
* Notice the predictable output of `demo`
```java
public void demo() {
    SynchronizedCounter runnable= new SynchronizedCounter();
    Thread first = new Thread(runnable);
    Thread second = new Thread(runnable);
    first.start();
    second.start();
    ThreadUtils.sleep(5000); // sleep 5 seconds
    System.out.println(runnable.getCount());
}
```
Output
```
THREAD FINISHED: Thread[Thread-0,5,main]
THREAD FINISHED: Thread[Thread-1,5,main]
2000000
```
-
-
## Synchronous Block
* `synchronous` keyword (which is an intrinsic lock)
* Useful when we want to synchronize part of method, but not all of it.
* Takes an argument of the object to be locked before allowing other thread access.
-
### Creating Synchronized Blocks
* Notice use of the `synchronized` block
```java
public class SynchronousCounter implements Runnable {
    private int count = 0;
    public void run() {
        for(int i = 0; i < 1_000_000; i++) {
            incrementCount();
        }
        String currentThread = Thread.currentThread().toString();
        System.out.println("THREAD FINISHED: " + currentThread);
    }
    public void incrementCount() {
        synchronized (this) {
            count += 1;
        }
    }
}
```
-
#### Using Synchronized Blocks
* Notice the predictable output of `demo`
```java
public void demo() {
    SynchronousCounter runnable= new SynchronousCounter();
    Thread first = new Thread(runnable);
    Thread second = new Thread(runnable);
    first.start();
    second.start();
    ThreadUtils.sleep(5000); // sleep 5 seconds
    System.out.println(runnable.getCount());
}
```
-
-
## Deadlocks
* When two or more threads are waiting for the other to release a lock.
-
### Encountering a Deadlock
Let's put together a situation that would cause a deadlock with our threads...
-
### Encountering a Deadlock: ThreadDemo1
```java
public class ThreadDemo1 extends Thread {
  public static Object lock1;
  public static Object lock2;
  public ThreadDemo1(Object lock1, Object lock2) {
    this.lock1 = lock1;
    this.lock2 = lock2;
  }
  public void run() {
    synchronized (lock1) {
      System.out.println("Thread 1: Holding lock 1...");
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {}
        System.out.println("Thread 1: Waiting for lock 2...");
      synchronized (lock2) {
        System.out.println("Thread 1: Holding lock 1 & 2...");
      }
    }
  }
}
```
-
### Encountering a Deadlock: ThreadDemo2
```java
public class ThreadDemo2 extends Thread {
  public static Object lock1;
  public static Object lock2;
  public ThreadDemo2(Object lock1, Object lock2) {
    this.lock1 = lock1;
    this.lock2 = lock2;
  }
  public void run() {
    synchronized (lock2) {
      System.out.println("Thread 2: Holding lock 2...");
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {}
      System.out.println("Thread 2: Waiting for lock 1...");
      synchronized (lock1) {
          System.out.println("Thread 2: Holding lock 1 & 2...");
      }
    }
  }
}
```
-
### Encountering a Deadlock
* Run the Demo:
```java
public class AsynchDemo {
    public static Object lock1 = new Object();
    public static Object lock2 = new Object();
    public static void main(String[] args) {
        ThreadDemo1 T1 = new ThreadDemo1(lock1, lock2);
        ThreadDemo2 T2 = new ThreadDemo2(lock1, lock2);
        T1.start();
        T2.start();
    }
}
```
*Output:
```
Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...
```
-
### Encountering a Deadlock : Fix
To fix this deadlock, we'll change the order of the locks on one of our ThreadDemo classes.
-
### Fixing a Deadlock: ThreadDemo1
(unchanged)
```java
public class ThreadDemo1 extends Thread {
  public static Object lock1;
  public static Object lock2;
  public ThreadDemo1(Object lock1, Object lock2) {
    this.lock1 = lock1;
    this.lock2 = lock2;
  }
  public void run() {
    synchronized (lock1) {
      System.out.println("Thread 1: Holding lock 1...");
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {}
        System.out.println("Thread 1: Waiting for lock 2...");
      synchronized (lock2) {
        System.out.println("Thread 1: Holding lock 1 & 2...");
      }
    }
  }
}
```
-
### Fixing a Deadlock: ThreadDemo2
(changed the order of the locks on ThreadDemo2)
```java
public class ThreadDemo2 extends Thread {
  public static Object lock1;
  public static Object lock2;
  public ThreadDemo2(Object lock1, Object lock2) {
    this.lock1 = lock1;
    this.lock2 = lock2;
  }
  public void run() {
    synchronized (lock1) {
      System.out.println("Thread 2: Holding lock 2...");
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {}
      System.out.println("Thread 2: Waiting for lock 1...");
      synchronized (lock2) {
          System.out.println("Thread 2: Holding lock 1 & 2...");
      }
    }
  }
}
```
-
### Encountering a Deadlock
* Now, run the Demo:
```java
public class AsynchDemo {
    public static Object lock1 = new Object();
    public static Object lock2 = new Object();
    public static void main(String[] args) {
        ThreadDemo1 T1 = new ThreadDemo1(lock1, lock2);
        ThreadDemo2 T2 = new ThreadDemo2(lock1, lock2);
        T1.start();
        T2.start();
    }
}
```
 * Output:
```
Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 2...
Thread 2: Waiting for lock 1...
Thread 2: Holding lock 1 & 2...
Process finished with exit code 0
```
-
-