Concurrency : Synchronization

Threads share the same memory space, i.e., they can share resources. However, there are critical situations where it is desirable that only one thread at a time has access to a shared resource. Let us take a simple example to problem accessing the shared resources


package mynotes.concurrency.basic;

public class SynchronizedMethodExample {

private int count = 0;

private void doWork() {
 Thread t1 = new Thread(new Runnable() {
 @Override
 public void run() {
 for (int i = 0; i < 1000; i++) {
 count=count+1;
 }

}
 });
 Thread t2 = new Thread(new Runnable() {
 @Override
 public void run() {
 for (int i = 0; i < 1000; i++) {
 count=count+1;
 }

}
 });

t1.start();
 t2.start();

try {
 t1.join();
 t2.join();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }

System.out.println(count);
 }

public static void main(String[] args) {
 SynchronizedMethodExample example = new SynchronizedMethodExample();
 example.doWork();
 }

}

In the above example, we have a int variable count as a member variable. The method doWork() creates 2 threads which increments the value of count. We added join() call on both method so as to wait for both methods to get over before printing the count in the sysout at the end. Now we may think the output will be ‘2000’ as both the threads in incrementing the count to 1000 and regardless in what sequence they access, the final output will be 2000. Here the output on multiple runs :

Run1 :
1524
Run2:
1833
Run3:
1824

This unexpected result is because the line ‘count=count+1’ is not a atomic operation i.e does not happens in one step. Instead its a combination of 3 steps. First, it reads the variable of the count. Secondly, it adds one to it and thirdly, it assigns the updated value to the count variable. As we know that threads runs at the mercy of your OS and will vary on each run. Suppose the current value of count is 5. t1 now is being run and it reads the count as 5 and now in going to the next step of adding 1, however in-between t2 thread got the chance to run which reads the value 5 and add 1 to it and assigns it to count. t1 threads got resumes again which still have count variable as 5 (instead of 6) and updates it to 6. Here we lost one increment. It could also happen that while t1 was updating the count , t2 got 5 chances to run. thus now, we lost 5 increments.

To solve this , we need to make sure that if a thread is accessing some variable, no other thread could get in between and change it. Since here we have just an int, we can make use of a AtomicInteger, but a more general way is to use synchronized keyword.

We will have another method in our class:


public synchronized int increment(){
 return count+1;
 }

Now replace ‘count=count+1’ with increment() inside your thread. Now whenever you run it, the output will always be 2000.

A lock (also called a monitor) is used to synchronize access to a shared resource. At any given time, at most one thread can hold the lock and thereby have access to the shared resource. A lock thus implements mutual exclusion (also known as mutex).

In Java, all objects have a lock—including arrays. The object lock mechanism enforces the following rules of synchronization:

  • A thread must acquire the object lock associated with a shared resource, before it can enter the shared resource. The runtime system ensures that no other thread can enter a shared resource if another thread already holds the object lock associated with it. If a thread cannot immediately acquire the object lock, it is blocked, i.e., it must wait for the lock to become available.
  • When a thread exits a shared resource, the runtime system ensures that the object lock is also relinquished. If another thread is waiting for this object lock, it can try to acquire the lock in order to gain access to the shared resource.

Classes also have a class specific lock that is analogous to the object lock. Such a lock is actually a lock on the java.lang.Class object associated with the class. Given a class A, the reference A.class denotes this unique Class object. The class lock can be used in much the same way as an object lock to implement mutual exclusion.

There are two ways in which execution of code can be synchronized, by declaring synchronized methods or synchronized code blocks.

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

A thread acquires the class lock before it can proceed with the execution of any static synchronized method in the class, blocking other threads wishing to execute any static synchronized methods in the same class. This does not apply to static, nonsynchronized methods, which can be invoked at any time. A thread acquiring the lock of a class to execute a static synchronized method has no effect on any thread acquiring the lock on any object of the class to execute a synchronized instance method. In other words, synchronization of static methods in a class is independent from the synchronization of instance methods on objects of the class.

A subclass decides whether the new definition of an inherited synchronized method will remain synchronized in the subclass.

Synchronized Blocks

Whereas execution of synchronized methods of an object is synchronized on the lock of the object, the synchronized block allows execution of arbitrary code to be synchronized on the lock of an arbitrary object.  Synchronized block is better than synchronized method because by using synchronized block you can only lock critical section of code and avoid locking whole method which can possibly degrade performance. The general form of the synchronized statement is as follows:

synchronized (<object reference expression>) {<code block> }

The <object reference expression> must evaluate to a non-null reference value, otherwise a NullPointerException is thrown.

Let us see the use by an example:


package mynotes.concurrency.basic;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class SynchronizationMadeSlow {

private List<Integer> l1 = new ArrayList<Integer>();
 private List<Integer> l2 = new ArrayList<Integer>();
 private Random random = new Random();

// Doing something - adding in l1

public synchronized void doTask1() {

l1.add(random.nextInt(100));
 try {
 Thread.sleep(1);
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }

// Doing something completely independent from doTask1 -adding in l2

public synchronized void doTask2() {

l2.add(random.nextInt(100));
 try {
 Thread.sleep(1);
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }

public void doBothTask() {
 for (int i = 0; i < 1000; i++) {
 doTask1();
 doTask2();
 }
 }

public void test() {
 Thread t1 = new Thread(new Runnable() {
 @Override
 public void run() {
 doBothTask();
 }
 });
 Thread t2 = new Thread(new Runnable() {
 @Override
 public void run() {
 doBothTask();
 }
 });

t1.start();
 t2.start();
 try {
 t1.join();
 t2.join();
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }

}

public static void main(String[] args) {
 System.out.println("Starting...");
 SynchronizationMadeSlow aSynchronizationMadeSlow = new SynchronizationMadeSlow();
 long start = System.currentTimeMillis();
 aSynchronizationMadeSlow.test();
 long end = System.currentTimeMillis();
 System.out.println("Total Time take==" + (end - start));
 }

}

Output:


Starting...
Total Time take==4629

Now each time you run this code, the time taken would be roughly over 4 sec as we are making the thread sleep for 1 milli second twice in a loop of 1000. We added the synchronized keyword so as to be safe from any data corruption as explained previously, but here when a thread is accessing doTask1() it has now the intrinsic lock of the object, so the thread 2 which can run doTaslk2() still have to wait for the lock to be available. Also note that doTask1() and doTask2() are completely independent methods. So we must allow thread2 to carry on with doTask2().

Lets see the synchronized block way:


package mynotes.concurrency.basic;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class SynchronizationMadeFast {

private List<Integer> l1 = new ArrayList<Integer>();
 private List<Integer> l2 = new ArrayList<Integer>();
 private Random random = new Random();

private Object lock1 = new Object();
 private Object lock2 = new Object();

// Doing something - adding in l1

public void doTask1() {
 synchronized (lock1) {
 l1.add(random.nextInt(100));
 try {
 Thread.sleep(1);
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 }

// Doing something completely independent from doTask1 -adding in l2

public void doTask2() {
 synchronized (lock2) {
 l2.add(random.nextInt(100));
 try {
 Thread.sleep(1);
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 }

public void doBothTask() {
 for (int i = 0; i < 1000; i++) {
 doTask1();
 doTask2();
 }
 }

public void test() {
 Thread t1 = new Thread(new Runnable() {
 @Override
 public void run() {
 doBothTask();
 }
 });
 Thread t2 = new Thread(new Runnable() {
 @Override
 public void run() {
 doBothTask();
 }
 });

t1.start();
 t2.start();
 try {
 t1.join();
 t2.join();
 } catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }

}

public static void main(String[] args) {
 System.out.println("Starting...");
 SynchronizationMadeFast aSynchronizationMadeFast = new SynchronizationMadeFast();
 long start = System.currentTimeMillis();
 aSynchronizationMadeFast.test();
 long end = System.currentTimeMillis();
 System.out.println("Total Time take==" + (end - start));
 }

}

Output:


Starting...
Total Time take==2227

Now 2 threads can run the same method, but if one thread enters the synchronized block the other thread has to wait, which may be beneficial as it may choose to run the other method instead of waiting, thus making the code more efficient.

Synchronized blocks can also be specified on a class lock:

synchronized (<class name>.class) { <code block> }

Synchronizing on an inner object and on its associated outer object are independent of each other, unless enforced explicitly, as in the following code:


class Outer { //  Toplevel Class
private double myPi;
protected class Inner {// Nonstatic member Class
public void setPi() {
synchronized(Outer.this) { // Synchronized block on outer object
myPi = Math.PI;
}}}}

Disadvantage of Java synchronized keyword:

  • It doesn’t allow concurrent read, which can potentially limit scalability. By using concept of lock stripping and using different locks for reading and writing, you can overcome this limitation of synchronized in Java.
  • it can only be used to control access of shared object within the same JVM. If you have more than one JVM and need to synchronized access to a shared file system or database, the Java synchronized keyword is not at all sufficient. You need to implement a kind of global lock for that.
  • slow and can degrade performance.

Other Important Points:

  • Do not synchronize on non final field on synchronized block in Java. because reference of non final field may change any time and then different thread might synchronizing on different objects i.e. no synchronization at all. example of synchronizing on non final field :
    private String lock = new String(“lock”);
    synchronized(lock){
    System.out.println(“locking on :”  + lock);

    }

  • Its not recommended to use String object as lock in java synchronized block because string is immutable object and literal string and interned string gets stored in String pool. so by any chance if any other part of code or any third party library used same String as there lock then they both will be locked on same object despite being completely unrelated which could result in unexpected behavior and bad performance.

 

Grab all the code from my GitHub repository.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: