Lock與synchronized 的區別

1、ReentrantLock 擁有Synchronized相同的併發性和內存語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候

     線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定,

     如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷

     如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情

 

    ReentrantLock獲取鎖定與三種方式:
    a)  lock(), 如果獲取了鎖立即返回,如果別的線程持有鎖,當前線程則一直處於休眠狀態,直到獲取鎖

    b) tryLock(), 如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;

    c)tryLock(long timeout,TimeUnit unit),   如果獲取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;

    d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前線程處於休眠狀態,直到或者鎖定,或者當前線程被別的線程中斷

 

2、synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過代碼實現的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中

 

3、在資源競爭不是很激烈的情況下,Synchronized的性能要優於ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態;


處處都好?

看起來 ReentrantLock 無論在哪方面都比 synchronized 好 —— 所有 synchronized 能做的,它都能做,它擁有與 synchronized 相同的內存和併發性語義,還擁有 synchronized 所沒有的特性,在負荷下還擁有更好的性能。那麼,我們是不是應當忘記 synchronized ,不再把它當作已經已經得到優化的好主意呢?或者甚至用ReentrantLock 重寫我們現有的 synchronized 代碼?實際上,幾本 Java 編程方面介紹性的書籍在它們多線程的章節中就採用了這種方法,完全用 Lock 來做示例,只把 synchronized 當作歷史。但我覺得這是把好事做得太過了。

不要拋棄 synchronized

雖然 ReentrantLock 是個非常動人的實現,相對 synchronized 來說,它有一些重要的優勢,但是我認爲急於把 synchronized 視若敝屣,絕對是個嚴重的錯誤。 java.util.concurrent.lock 中的鎖定類是用於高級用戶和高級情況的工具 。一般來說,除非您對 Lock 的某個高級特性有明確的需要,或者有明確的證據(而不是僅僅是懷疑)表明在特定情況下,同步已經成爲可伸縮性的瓶頸,否則還是應當繼續使用 synchronized。

爲什麼我在一個顯然“更好的”實現的使用上主張保守呢?因爲對於 java.util.concurrent.lock 中的鎖定類來說,synchronized 仍然有一些優勢。比如,在使用 synchronized 的時候,不能忘記釋放鎖;在退出synchronized 塊時,JVM 會爲您做這件事。您很容易忘記用 finally 塊釋放鎖,這對程序非常有害。您的程序能夠通過測試,但會在實際工作中出現死鎖,那時會很難指出原因(這也是爲什麼根本不讓初級開發人員使用Lock 的一個好理由。)

另一個原因是因爲,當 JVM 用 synchronized 管理鎖定請求和釋放時,JVM 在生成線程轉儲時能夠包括鎖定信息。這些對調試非常有價值,因爲它們能標識死鎖或者其他異常行爲的來源。 Lock 類只是普通的類,JVM 不知道具體哪個線程擁有 Lock 對象。而且,幾乎每個開發人員都熟悉 synchronized,它可以在 JVM 的所有版本中工作。在 JDK 5.0 成爲標準(從現在開始可能需要兩年)之前,使用 Lock 類將意味着要利用的特性不是每個 JVM 都有的,而且不是每個開發人員都熟悉的。

什麼時候選擇用 ReentrantLock 代替 synchronized

既然如此,我們什麼時候才應該使用 ReentrantLock 呢?答案非常簡單 —— 在確實需要一些 synchronized 所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。 ReentrantLock還具有可伸縮性的好處,應當在高度爭用的情況下使用它,但是請記住,大多數 synchronized 塊幾乎從來沒有出現過爭用,所以可以把高度爭用放在一邊。我建議用 synchronized 開發,直到確實證明 synchronized 不合適,而不要僅僅是假設如果使用 ReentrantLock “性能會更好”。請記住,這些是供高級用戶使用的高級工具。(而且,真正的高級用戶喜歡選擇能夠找到的最簡單工具,直到他們認爲簡單的工具不適用爲止。)。一如既往,首先要把事情做好,然後再考慮是不是有必要做得更快。

Lock 框架是同步的兼容替代品,它提供了 synchronized 沒有提供的許多特性,它的實現在爭用下提供了更好的性能。但是,這些明顯存在的好處,還不足以成爲用 ReentrantLock 代替 synchronized 的理由。相反,應當根據您是否 需要 ReentrantLock 的能力來作出選擇。大多數情況下,您不應當選擇它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的開發人員瞭解它,而且不太容易出錯。只有在真正需要 Lock 的時候才用它。在這些情況下,您會很高興擁有這款工具。



各種同步方法性能比較(synchronized,ReentrantLock,Atomic)參考http://zzhonghe.iteye.com/blog/826162

5.0的多線程任務包對於同步的性能方面有了很大的改進,在原有synchronized關鍵字的基礎上,又增加了ReentrantLock,以及各種Atomic類。瞭解其性能的優劣程度,有助與我們在特定的情形下做出正確的選擇。 

總體的結論先擺出來:  

synchronized: 
在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在於,編譯程序通常會儘可能的進行優化synchronize,另外可讀性非常好,不管用沒用過5.0多線程包的程序員都能理解。 

ReentrantLock: 
ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,性能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的性能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。 

Atomic: 
和上面的類似,不激烈情況下,性能比synchronized略遜,而激烈的時候,也能維持常態。激烈的時候,Atomic的性能會優於ReentrantLock一倍左右。但是其有一個缺點,就是隻能同步一個值,一段代碼中只能出現一個Atomic的變量,多於一個同步無效。因爲他不能在多個Atomic之間同步。 


所以,我們寫同步的時候,優先考慮synchronized,如果有特殊需要,再進一步優化。ReentrantLock和Atomic如果用的不好,不僅不能提高性能,還可能帶來災難。 

先貼測試結果:再貼代碼(Atomic測試代碼不準確,一個同步中只能有1個Actomic,這裏用了2個,但是這裏的測試只看速度) 
========================== 
round:100000 thread:5 
Sync = 35301694 
Lock = 56255753 
Atom = 43467535 
========================== 
round:200000 thread:10 
Sync = 110514604 
Lock = 204235455 
Atom = 170535361 
========================== 
round:300000 thread:15 
Sync = 253123791 
Lock = 448577123 
Atom = 362797227 
========================== 
round:400000 thread:20 
Sync = 16562148262 
Lock = 846454786 
Atom = 667947183 
========================== 
round:500000 thread:25 
Sync = 26932301731 
Lock = 1273354016 
Atom = 982564544 


Java代碼  收藏代碼
  1. package test.thread;  
  2.   
  3. import static java.lang.System.out;  
  4.   
  5. import java.util.Random;  
  6. import java.util.concurrent.BrokenBarrierException;  
  7. import java.util.concurrent.CyclicBarrier;  
  8. import java.util.concurrent.ExecutorService;  
  9. import java.util.concurrent.Executors;  
  10. import java.util.concurrent.atomic.AtomicInteger;  
  11. import java.util.concurrent.atomic.AtomicLong;  
  12. import java.util.concurrent.locks.ReentrantLock;  
  13.   
  14. public class TestSyncMethods {  
  15.       
  16.     public static void test(int round,int threadNum,CyclicBarrier cyclicBarrier){  
  17.         new SyncTest("Sync",round,threadNum,cyclicBarrier).testTime();  
  18.         new LockTest("Lock",round,threadNum,cyclicBarrier).testTime();  
  19.         new AtomicTest("Atom",round,threadNum,cyclicBarrier).testTime();  
  20.     }  
  21.   
  22.     public static void main(String args[]){  
  23.           
  24.         for(int i=0;i<5;i++){  
  25.             int round=100000*(i+1);  
  26.             int threadNum=5*(i+1);  
  27.             CyclicBarrier cb=new CyclicBarrier(threadNum*2+1);  
  28.             out.println("==========================");  
  29.             out.println("round:"+round+" thread:"+threadNum);  
  30.             test(round,threadNum,cb);  
  31.               
  32.         }  
  33.     }  
  34. }  
  35.   
  36. class SyncTest extends TestTemplate{  
  37.     public SyncTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){  
  38.         super( _id, _round, _threadNum, _cb);  
  39.     }  
  40.     @Override  
  41.     /** 
  42.      * synchronized關鍵字不在方法簽名裏面,所以不涉及重載問題 
  43.      */  
  44.     synchronized long  getValue() {  
  45.         return super.countValue;  
  46.     }  
  47.     @Override  
  48.     synchronized void  sumValue() {  
  49.         super.countValue+=preInit[index++%round];  
  50.     }  
  51. }  
  52.   
  53.   
  54. class LockTest extends TestTemplate{  
  55.     ReentrantLock lock=new ReentrantLock();  
  56.     public LockTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){  
  57.         super( _id, _round, _threadNum, _cb);  
  58.     }  
  59.     /** 
  60.      * synchronized關鍵字不在方法簽名裏面,所以不涉及重載問題 
  61.      */  
  62.     @Override  
  63.     long getValue() {  
  64.         try{  
  65.             lock.lock();  
  66.             return super.countValue;  
  67.         }finally{  
  68.             lock.unlock();  
  69.         }  
  70.     }  
  71.     @Override  
  72.     void sumValue() {  
  73.         try{  
  74.             lock.lock();  
  75.             super.countValue+=preInit[index++%round];  
  76.         }finally{  
  77.             lock.unlock();  
  78.         }  
  79.     }  
  80. }  
  81.   
  82.   
  83. class AtomicTest extends TestTemplate{  
  84.     public AtomicTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){  
  85.         super( _id, _round, _threadNum, _cb);  
  86.     }  
  87.     @Override  
  88.     /** 
  89.      * synchronized關鍵字不在方法簽名裏面,所以不涉及重載問題 
  90.      */  
  91.     long  getValue() {  
  92.         return super.countValueAtmoic.get();  
  93.     }  
  94.     @Override  
  95.     void  sumValue() {  
  96.         super.countValueAtmoic.addAndGet(super.preInit[indexAtomic.get()%round]);  
  97.     }  
  98. }  
  99. abstract class TestTemplate{  
  100.     private String id;  
  101.     protected int round;  
  102.     private int threadNum;  
  103.     protected long countValue;  
  104.     protected AtomicLong countValueAtmoic=new AtomicLong(0);  
  105.     protected int[] preInit;  
  106.     protected int index;  
  107.     protected AtomicInteger indexAtomic=new AtomicInteger(0);  
  108.     Random r=new Random(47);  
  109.     //任務柵欄,同批任務,先到達wait的任務掛起,一直等到全部任務到達制定的wait地點後,才能全部喚醒,繼續執行  
  110.     private CyclicBarrier cb;  
  111.     public TestTemplate(String _id,int _round,int _threadNum,CyclicBarrier _cb){  
  112.         this.id=_id;  
  113.         this.round=_round;  
  114.         this.threadNum=_threadNum;  
  115.         cb=_cb;  
  116.         preInit=new int[round];  
  117.         for(int i=0;i<preInit.length;i++){  
  118.             preInit[i]=r.nextInt(100);  
  119.         }  
  120.     }  
  121.       
  122.     abstract void sumValue();  
  123.     /* 
  124.      * 對long的操作是非原子的,原子操作只針對32位 
  125.      * long是64位,底層操作的時候分2個32位讀寫,因此不是線程安全 
  126.      */  
  127.     abstract long getValue();  
  128.   
  129.     public void testTime(){  
  130.         ExecutorService se=Executors.newCachedThreadPool();  
  131.         long start=System.nanoTime();  
  132.         //同時開啓2*ThreadNum個數的讀寫線程  
  133.         for(int i=0;i<threadNum;i++){  
  134.             se.execute(new Runnable(){  
  135.                 public void run() {  
  136.                     for(int i=0;i<round;i++){  
  137.                         sumValue();  
  138.                     }  
  139.   
  140.                     //每個線程執行完同步方法後就等待  
  141.                     try {  
  142.                         cb.await();  
  143.                     } catch (InterruptedException e) {  
  144.                         // TODO Auto-generated catch block  
  145.                         e.printStackTrace();  
  146.                     } catch (BrokenBarrierException e) {  
  147.                         // TODO Auto-generated catch block  
  148.                         e.printStackTrace();  
  149.                     }  
  150.   
  151.   
  152.                 }  
  153.             });  
  154.             se.execute(new Runnable(){  
  155.                 public void run() {  
  156.   
  157.                     getValue();  
  158.                     try {  
  159.                         //每個線程執行完同步方法後就等待  
  160.                         cb.await();  
  161.                     } catch (InterruptedException e) {  
  162.                         // TODO Auto-generated catch block  
  163.                         e.printStackTrace();  
  164.                     } catch (BrokenBarrierException e) {  
  165.                         // TODO Auto-generated catch block  
  166.                         e.printStackTrace();  
  167.                     }  
  168.   
  169.                 }  
  170.             });  
  171.         }  
  172.           
  173.         try {  
  174.             //當前統計線程也wait,所以CyclicBarrier的初始值是threadNum*2+1  
  175.             cb.await();  
  176.         } catch (InterruptedException e) {  
  177.             // TODO Auto-generated catch block  
  178.             e.printStackTrace();  
  179.         } catch (BrokenBarrierException e) {  
  180.             // TODO Auto-generated catch block  
  181.             e.printStackTrace();  
  182.         }  
  183.         //所有線程執行完成之後,纔會跑到這一步  
  184.         long duration=System.nanoTime()-start;  
  185.         out.println(id+" = "+duration);  
  186.           
  187.     }  
  188.   
  189. }  



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