Synchronized Methods – Concurrency: Part I

Synchronized Methods

If the methods of an object should only be executed by one thread at a time, then the declaration of all such methods should be specified with the keyword synchronized. A thread wishing to execute a synchronized method must first acquire the object lock before it can execute the method on the object. This is simply achieved by calling the method. If the lock is already held by another thread, the calling thread waits in the entry set of the object lock, as explained earlier. No particular action on the part of the program is necessary to acquire the lock. A thread releases the lock on method return, regardless of whether the synchronized method completed execution normally or threw an uncaught exception. In both cases, if there are any blocked threads in the entry set, one is chosen to acquire the lock. In case of an uncaught exception, the exception is propagated through the JVM stack of the thread that released the lock.

Synchronized methods are useful in situations where methods can manipulate the state of a shared object in ways that can corrupt the state if executed concurrently.

Example 22.4 is a reworking of the counter implementation from Example 22.1. The CounterX class defines the synchronized method increment() at (1a) that prints the old value and the new incremented value in the counter.

The main() method in the SynchronizedMethodDemo class creates a CounterX object at (2) and a Runnable at (3) that calls the increment() method five times on this CounterX object. Two threads are created and started at (4) and (5) that will each call the synchronized increment() method on the same CounterX object five times—in contrast to Example 22.1, where each thread had its own counter.

From the output shown in Example 22.4, we can see that the main thread exits right after creating and starting the threads. The output shows that the two threads run mutually exclusive of each other. Thread-0 increments the counter to 5 and Thread-1 increments it further to 10.

It is instructive to run Example 22.4 with (1a) commented out and (1b) uncommented. We see from the output that the execution of the threads is interleaved, and some results look dubious (Thread-1: old:0 new:2). Non-synchronized incrementing of the value in the counter between the two threads is a disaster waiting to happen. This is an example of what is called a race condition (also known as thread interference). It occurs when two or more threads simultaneously update a shared value and, due to the scheduling of the threads, can leave the value in an undefined or inconsistent state.

Running the program in Example 22.4 with the synchronized version of the increment() at (1a) avoids any race conditions. The lock is only released when the synchronized method exits, guaranteeing a mutually exclusive increment of the counter by each thread.

Example 22.4 Mutual Exclusion

Click here to view code image

class CounterX {
  private int counter = 0;
  public synchronized void increment() {                            // (1a)
//public void increment() {                                         // (1b)
    System.out.println(Thread.currentThread().getName()
                     + “: old:” + counter + ” new:” + ++counter);
  }
}

public class SynchronizedMethodDemo {
  public static void main(String[] args) {
    CounterX counter = new CounterX();                              // (2)
    Runnable r = () -> {                                            // (3)
      for (int i = 0; i < 5; i++) {
        counter.increment();
      }
      System.out.println(“Exiting ” + Thread.currentThread().getName());
    };
    new Thread(r).start();                                          // (4)
    new Thread(r).start();                                          // (5)
    System.out.println(“Exiting thread ” + Thread.currentThread().getName());
  }
}

Probable output from the program when run with (1a):

Exiting thread main
Thread-0: old:0 new:1
Thread-0: old:1 new:2
Thread-0: old:2 new:3
Thread-0: old:3 new:4
Thread-0: old:4 new:5
Exiting Thread-0
Thread-1: old:5 new:6
Thread-1: old:6 new:7
Thread-1: old:7 new:8
Thread-1: old:8 new:9
Thread-1: old:9 new:10
Exiting Thread-1

Probable output from the program when run with (1b):

Thread-1: old:0 new:2
Thread-1: old:2 new:3
Exiting thread main
Thread-0: old:0 new:1
Thread-1: old:3 new:4
Thread-0: old:4 new:5
Thread-0: old:6 new:7
Thread-1: old:5 new:6
Thread-1: old:8 new:9
Exiting Thread-1
Thread-0: old:7 new:8
Thread-0: old:9 new:10
Exiting Thread-0

Leave a Reply

Your email address will not be published. Required fields are marked *