2016年7月22日 星期五

What the difference are between synchronized this vs class?


Locking in Java multi-threading / concurrency environment could actually be implemented as 2 different level:
(1)   Object-level (instance-level)
(2)   Class-level

The passage of How important and how to implement tread-safe in Java concurrency / multithreading environment? has revealed different ways of implementing object-level (or instance-level) locking.

The 3 ways introduced there could guarantee thread-safety for most cases. However, there is still one circumstance which would let it expose to race condition dilemma (not thread-safe).


Let’s revise the main() method of the example code there a little bit and take a look:

public static void main(String[] args){
     Parent counterA = new Parent();
     Parent counterB = new Parent();
     Child a = counterA.new Child("A", counterA, 5);
     Child b = counterA.new Child("B", counterA, 10);
    
     Child c = counterB.new Child("C", counterB, 5);
     Child d = counterB.new Child("D", counterB, 10);
    
     a.start();
     b.start();
    
     c.start();
     d.start();
    
}


The revised full version of the example looks like:

public class Parent {
     private static int counter = 0;
    
     public int getCount(long millis){
        
         try {
              // Pretend heavy-loading job here
              Thread.sleep(millis);
         } catch (InterruptedException e) {
         }
        
         synchronized(this){
              return ++counter;
         }
     }
    
     class Child extends Thread{
         private final String name;
         private final Parent counter;
         private final long millis;
        
         public Child(final String name, final Parent counter, final long millis){
              this.name = name;
              this.counter = counter;
              this.millis = millis;
         }
        
         public void run(){
              for (int i = 0; i < 50; i++){
                  synchronized (Parent.this){
                       int oCounter = counter.getCount(millis);
                       System.out.println(name + ": " + oCounter);
                  }
              }
         }
     }
    
     public static void main(String[] args){
         Parent counterA = new Parent();
         Parent counterB = new Parent();
         Child a = counterA.new Child("A", counterA, 5);
         Child b = counterA.new Child("B", counterA, 10);
        
         Child c = counterB.new Child("C", counterB, 5);
         Child d = counterB.new Child("D", counterB, 10);
        
         a.start();
         b.start();
        
         c.start();
         d.start();
        
     }
}


After running several times, you may discover that the final largest result is varying between 17x to 200, instead of 200 constantly.



The reason is simple. Since previously mentioned locking approaches are only synchronizing with object-level (or instance-level) lock on the Parent class (i.e. callee class), this would result into race condition again as the callee class has been instantiated for more than once (i.e. counterA, counterB) as shown in the example above.

For resolving the potential race condition if we forsee the callee class is possible to be instantiated for more than once, we can apply class-level synchronization.

In usual, synchronized(this) which we see usually is object-level (i.e. instance-level) locking. While synchronized(getClass()) is class-level locking.


Class-level synchronization

Class-level synchronization could usually implemented as block-synchronization style on the method of either: (1) the caller class; or (2) the callee class.


Approach 1 - Class-level block synchronization on method of callee class (i.e. Parent class)

Adding synchronized(this.getClass()) to the area of codes which involves operations on static variable. Such as:  

public int getCount(long millis){
    
     try {
         // Pretend heavy-loading job here
         Thread.sleep(millis);
     } catch (InterruptedException e) { }
    
     synchronized(this.getClass()){
         return ++counter;
     }
}


Approach 2 - Class-level block synchronization on method of caller class (i.e. Child class)

Adding synchronized(Parent.class) to the area of codes which involves operations on static variable. Such as:  

public void run(){
     for (int i = 0; i < 50; i++){
         synchronized (Parent.class){
              int oCounter = counter.getCount(millis);
              System.out.println(name + ": " + oCounter);
         }
     }
}


Approach 3 - Class-level method synchronization on method of callee class

If it is preferred to implemented class-level synchronization as method-synchronization, it is still possible, but you should make the method being called as "static" first. (i.e. public static synchronized int getCount(){ .. } )Such as:

public static synchronized int getCount(long millis){
    
     try {
         // Pretend heavy-loading job here
         Thread.sleep(millis);
     } catch (InterruptedException e) { }
    
     // synchronized(this.getClass()){
         return ++counter;
     // }
}

  
The final revised example would be as below which I have enabled Approach 1 but still remained Approach 2 as commented for reference:

/**
 *
 * @author Gary Wong
 *
 */
public class Parent {
     private static int counter = 0;
    
     public int getCount(long millis){
        
         try {
              // Pretend heavy-loading job here
              Thread.sleep(millis);
         } catch (InterruptedException e) { }
        
         // This is class-level lock to guarantee thread-safe in different circumstances
         synchronized(this.getClass()){
              return ++counter;
         }
     }
    
     class Child extends Thread{
         private final String name;
         private final Parent counter;
         private final long millis;
        
         public Child(final String name, final Parent counter, final long millis){
              this.name = name;
              this.counter = counter;
              this.millis = millis;
         }
        
         public void run(){
              for (int i = 0; i < 50; i++){
                   // This is class-level lock to guarantee thread-safe in different circumstances
                   //synchronized (Parent.class){
                       int oCounter = counter.getCount(millis);
                       System.out.println(name + ": " + oCounter);
                   //}
              }
         }
     }
    
     public static void main(String[] args){
         Parent counterA = new Parent();
         Parent counterB = new Parent();
         // long start = System.nanoTime();
         // long start = System.currentTimeMillis();
         Child a = counterA.new Child("A", counterA, 5);
         Child b = counterA.new Child("B", counterA, 10);
        
         Child c = counterB.new Child("C", counterB, 5);
         Child d = counterB.new Child("D", counterB, 10);
        
         a.start();
         b.start();
        
         c.start();
         d.start();
         /*
         try {
              a.join();
              b.join();
              c.join();
              c.join();
         } catch (InterruptedException e) {
              e.printStackTrace();
         }
         */
         // long end = System.nanoTime();
         // long end = System.currentTimeMillis();
         // System.out.println("Completion duration: " + (end - start));
     }
}


 Here we come to the expected result:

 


Conclusion


While considering both thread-safety and performance, Approach 1 (i.e. Class-level block synchronization on method of callee class) is obviously doing advantage over the other approaches. It is because this approach is only synchronizing the minimum area of codes as lock has its cost of time.

Hope you enjoy this article.


Reference:

http://stackoverflow.com/questions/23261120/difference-between-class-locking-and-object-locking-in-java
http://www.programcreek.com/2014/02/how-to-make-a-method-thread-safe-in-java/
http://tutorials.jenkov.com/java-concurrency/index.html
http://tutorials.jenkov.com/java-concurrency/race-conditions-and-critical-sections.html#