Java線程和多線程(十四)——Synchronized關鍵字解析

曾經有一個比較有趣的面試問題,那就是,關於使用synchronized關鍵字,是用在方法上面尾號,還是用在一個代碼塊上面爲好?

答案就是使用鎖定代碼塊爲更好。因爲這樣不會鎖定對象。當synchronized關鍵字在實例方法的上面時,線程對於該方法的訪問會直接鎖定整個對象,參考如下代碼:

class Sync {
   public synchronized void syncMethod() {
      System.out.println("Sync Method Executed");
   }

   public void syncThis() {
      synchronized (this) {
         System.out.println("Sync This Executed");
      }
   }
}

上面的類中,syncMethod()syncThis()方法,是沒有區別的。爲了驗證這個說法,參考如下代碼:

package net.ethanpark.common.task;

/**
 * Author: EthanPark <br/>
 * Date: 2016/11/27<br/>
 */
public class SyncTest {
   public static void main(String[] args) {
      Sync sync = new Sync();

      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            sync.syncMethod();
         }
      });

      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            sync.syncThis();
         }
      });

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

class Sync {
   public synchronized void syncMethod() {
      System.out.println("Sync Method Start To Executed");
      try {
         Thread.sleep(5000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("Sync Method Executed");
   }

   public void syncThis() {
      synchronized (this) {
         System.out.println("Sync This Executed");
      }
   }
}

再說上面的代碼中,我啓動了兩個線程,分別執行syncMethod()syncThis()方法,其中syncMethod()方法會等待5秒,然後才執行結束。以下是輸出的結果:

Sync Method Start To Executed
Sync Method Executed
Sync This Executed

t1和t2是先後啓動的,如果syncMethod()方法沒有鎖定Sync對象的話,syncThis()方法肯定會更優先的執行,但是syncThis()方法從輸出來看,在獲取this對象的鎖的時候,阻塞了,直到syncMethod()全部執行完畢纔開始執行,所以纔有如上的輸出。

在考慮前面那個問題就清晰了,synchronized直接加在方法上面,會鎖定整個對象,也就是說,如果對象中有一些字段是不需要同時來同步的,synchronized關鍵字也會阻止其他線程來獲取該對象的鎖,從而令其他線程阻塞而無法達到高吞吐量。

當然,synchronized關鍵字對對象加鎖,也只有其他對象也在請求對象鎖的時候纔會阻塞的,如果其他對象不需要請求對象鎖,而是直接訪問非同步變量,還是允許的。針對之前的代碼我增加了一個新的線程和一個新的方法notSyncMethod():

package net.ethanpark.common.task;

/**
 * Author: EthanPark <br/>
 * Date: 2016/11/27<br/>
 */
public class SyncTest {
   public static void main(String[] args) {
      Sync sync = new Sync();

      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            sync.syncMethod();
         }
      });

      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            sync.syncThis();
         }
      });

      Thread t3 = new Thread(new Runnable() {
         @Override
         public void run() {
            sync.notSyncMethod();
         }
      });

      t1.start();
      t2.start();
      t3.start();
   }
}

class Sync {
   public synchronized void syncMethod() {
      System.out.println("Sync Method Start To Executed");
      try {
         Thread.sleep(5000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("Sync Method Executed");
   }

   public void syncThis() {
      synchronized (this) {
         System.out.println("Sync This Executed");
      }
   }

   public void notSyncMethod(){
      System.out.println("Not Sync Method Executed");
   }
}

程序的輸出如下:

Sync Method Start To Executed
Not Sync Method Executed
Sync Method Executed
Sync This Executed

從第一行輸出來看,t1其實已經獲得了sync對象的對象鎖,但是notSyncMethod()方法仍然是可以執行的。

需要注意的是,synchronized方法也是可以加在靜態方法上面的。然而加在靜態方法上面和加在實例方法上面,兩者是不會使得線程互斥的。原因很簡單,針對實例方法同步,我們請求的是對象鎖,而靜態方法的訪問,是不針對對象的,所以兩者是不會衝突的。針對靜態方法的同步,實際上跟針對類型的同步是一致的。參考如下代碼:

package net.ethanpark.common.task;

/**
 * Author: EthanPark <br/>
 * Date: 2016/11/27<br/>
 */
public class SyncTest {
   public static void main(String[] args) {
      Sync sync = new Sync();

      Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
            sync.syncMethod();
         }
      });

      Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
            Sync.staticSyncMethod();
         }
      });

      Thread t3 = new Thread(new Runnable() {
         @Override
         public void run() {
            Sync.staticTypeMethod();
         }
      });

      t1.start();
      t2.start();
      t3.start();
   }
}

class Sync {
   public synchronized void syncMethod() {
      System.out.println("Sync Method Start To Executed");
      try {
         Thread.sleep(5000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("Sync Method Executed");
   }

   public synchronized static void staticSyncMethod(){
      System.out.println("Static Sync Method Start To Executed");
      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("Static Sync Method Executed");
   }

   public static void staticTypeMethod(){
      synchronized (Sync.class){
         System.out.println("Static Sync Type Executed");
      }
   }
}

上面代碼的輸出如下:

Sync Method Start To Executed
Static Sync Method Start To Executed
Static Sync Method Executed
Static Sync Type Executed
Sync Method Executed

可以看出,在staticSyncMethod()staticTypeMethod()兩者是等同的,當staticSyncMethod()執行的時候,staticTypeMethod()是阻塞的。而兩個靜態的方法,跟實例的方法是完全互不影響的。

發佈了44 篇原創文章 · 獲贊 24 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章