synchronized 與 Lock 的區別

最近在作一個監控系統,該系統主要包括對數據實時分析和存儲兩個部分,因爲併發量比較高,因此不可避免的使用到了一些併發的知識。爲了 實現這些要求,後臺使用一個隊列做爲緩存,對於請求只管往緩存裏寫數據。同時啓動一個線程監聽該隊列,檢測到數據,當即請求調度線程,對數據進行處理。 具體的使用方案就是使用同步保證數據的正常,使用線程池提升效率。 java

 
同步的實現固然是採用鎖了,java中使用鎖的兩個基本工具是 synchronized 和 Lock。
 
一直很喜歡synchronized,由於使用它很方便。好比,須要對一個方法進行同步,那麼只需在方法的簽名添加一個synchronized關鍵字。
 
// 未同步的方法
public void test() {}
// 同步的方法
pubilc synchronized void test() {}
 
synchronized 也能夠用在一個代碼塊上,看
 
public void test() {
     synchronized(obj) {
          System.out.println("===");
     }
}
 
synchronized 用在方法和代碼塊上有什麼區別呢?
 
synchronized 用在方法簽名上(以test爲例),當某個線程調用此方法時,會獲取該實例的對象鎖,方法未結束以前,其餘線程只能去等待。當這個方法執行完時,纔會釋放 對象鎖。其餘線程纔有機會去搶佔這把鎖,去執行方法test,可是發生這一切的基礎應當是全部線程使用的同一個對象實例,才能實現互斥的現象。不然 synchronized關鍵字將失去意義。
 
可是若是該方法爲類方法,即其修飾符爲static,那麼synchronized 意味着某個調用此方法的線程當前會擁有該類的鎖,只要該線程持續在當前方法內運行,其餘線程依然沒法得到方法的使用權!
 
synchronized 用在代碼塊的使用方式:synchronized(obj){//todo code here}
 
當線程運行到該代碼塊內,就會擁有obj對象的對象鎖,若是多個線程共享同一個Object對象,那麼此時就會造成互斥!特別的,當obj == this時,表示當前調用該方法的實例對象。即
 
public void test() {
     ...
     synchronized(this) {
          // todo your code
     }
     ...
}
 
此時,其效果等同於
public synchronized void test() {
     // todo your code
}
 
 
使用synchronized代碼塊,能夠只對須要同步的代碼進行同步,這樣能夠大大的提升效率。
 
小結:
使用synchronized 代碼塊相比方法有兩點優點:
一、能夠只對須要同步的使用
二、與wait()/notify()/nitifyAll()一塊兒使用時,比較方便
 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 
wait() 與notify()/notifyAll()
 
這三個方法都是Object的方法,並非線程的方法!
wait():釋放佔有的對象鎖,線程進入等待池,釋放cpu,而其餘正在等待的線程便可搶佔此鎖,得到鎖的線程便可運行程序。而 sleep()不一樣的是,線程調用此方法後,會休眠一段時間,休眠期間,會暫時釋放cpu,但並不釋放對象鎖。也就是說,在休眠期間,其餘線程依然沒法進 入此代碼內部。休眠結束,線程從新得到cpu,執行代碼。 wait()和sleep()最大的不一樣在於wait()會釋放對象鎖,而sleep()不會!
 
notify(): 該方法會喚醒由於調用對象的wait()而等待的線程,其實就是 對對象鎖的喚醒,從而使得wait()的線程能夠有機會獲取對象鎖。調用notify()後,並不會當即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼所有執行完畢,纔會釋放對象鎖。JVM則會在等待的線程中調度一個線程去得到對象鎖,執行代碼。須要注意的是, wait()和notify()必須在synchronized代碼塊中調用
 
notifyAll()則是喚醒全部等待的線程。
 
爲了說明這一點,舉例以下:
兩個線程依次打印"A""B",總共打印10次。
 
public  class  Consumer  implements  Runnable {
 
      @Override
      public  synchronized  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while (count > 0) {
                  synchronized  (Test.  obj ) {
                     
                     System.  out .print(  "B" );
                     count --;
                     Test.  obj .notify();  // 主動釋放對象鎖
                     
                       try  {
                           Test.  obj .wait();
                           
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
                
           }
     }
}
 
public  class  Produce  implements  Runnable {
 
      @Override
      public  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while (count > 0) {
                  synchronized  (Test.  obj ) {
                     
                       //System.out.print("count = " + count);
                     System.  out .print(  "A" );
                     count --;
                     Test.  obj .notify();
                     
                       try  {
                           Test.  obj .wait();
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
                
           }
 
     }
 
}
 
測試類以下:
 
public  class  Test {
 
      public  static  final  Object  obj  =  new  Object();
     
      public  static  void  main(String[] args) {
           
             new  Thread(  new  Produce()).start();
             new  Thread(  new  Consumer()).start();
           
     }
}
 
這裏使用static obj做爲鎖的對象,當線程Produce啓動時(假如Produce首先得到鎖,則Consumer會等待),打印「A」後,會先主動釋放鎖,而後阻塞 本身。Consumer得到對象鎖,打印「B」,而後釋放鎖,阻塞本身,那麼Produce又會得到鎖,而後...一直循環下去,直到count = 0.這樣,使用Synchronized和wait()以及notify()就能夠達到線程同步的目的。
 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 
除了wait()和notify()協做完成線程同步以外,使用Lock也能夠完成一樣的目的。
 
ReentrantLock 與synchronized有相同的併發性和內存語義,還包含了中斷鎖等候和定時鎖等候,意味着線程A若是先得到了對象obj的鎖,那麼線程B能夠在等待指定時間內依然沒法獲取鎖,那麼就會自動放棄該鎖。
 
可是因爲synchronized是在JVM層面實現的,所以系統能夠監控鎖的釋放與否,而ReentrantLock使用代碼實現的,系統沒法自動釋放鎖,須要在代碼中finally子句中顯式釋放鎖lock.unlock();
 
一樣的例子,使用lock 如何實現呢?
 
public  class  Consumer  implements  Runnable {
 
      private  Lock  lock ;
      public  Consumer(Lock lock) {
             this .  lock  = lock;
     }
      @Override
      public  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while ( count > 0 ) {
                  try  {
                       lock .lock();
                     count --;
                     System.  out .print(  "B" );
                }  finally  {
                       lock .unlock(); //主動釋放鎖
                       try  {
                           Thread. sleep(91L);
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
 
     }
 
}
 
public  class  Producer  implements  Runnable{
 
      private  Lock  lock ;
      public  Producer(Lock lock) {
             this .  lock  = lock;
     }
      @Override
      public  void  run() {
             //  TODO  Auto-generated method stub
             int  count = 10;
             while  (count > 0) {
                  try  {
                       lock .lock();
                     count --;
                     System.  out .print(  "A" );
                }  finally  {
                       lock .unlock();
                       try  {
                           Thread. sleep(90L);
                     }  catch  (InterruptedException e) {
                             //  TODO  Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
     }
}
 
調用代碼:
 
public  class  Test {
 
      public  static  void  main(String[] args) {
           Lock lock =  new  ReentrantLock();
           
           Consumer consumer =  new  Consumer(lock);
           Producer producer =  new  Producer(lock);
           
             new  Thread(consumer).start();
             new  Thread( producer).start();
           
     }
}
 
 
使用建議:
 
在併發量比較小的狀況下,使用synchronized是個不錯的選擇,可是在併發量比較高的狀況下,其性能降低很嚴重,此時ReentrantLock是個不錯的方案。
相關文章
相關標籤/搜索