在編寫多線程代碼的時候,對於不容許併發的代碼,不少須要加鎖進行處理。在進行加鎖處理時候,synchronized做爲java的內置鎖,同時也是java關鍵字,最爲被人熟知,即便是最初級的java程序員,只要知道java併發處理的,都會知道syschronized。java
java5.0以後,java提供了另一種加鎖機制,ReentrantLock提供了更多更靈活的功能,在不少複雜場景下,ReentrantLock相比synchronized會更合適。程序員
synchronized和ReentrantLock也常常會拿出來比較,這也是在面試中,面試官也常常會問到的一個問題。這裏就簡單整理一下synchronized與ReentrantLock的異同。 面試
比較synchronized 和 ReentrantLock的相同點,毋庸置疑,它們的功能都是做爲鎖對訪問進行控制。防止併發形成邏輯錯誤。算法
一、在獲取ReentrantLock的時候,有着與synchronized相同的互斥性(互斥性:當一個線程已經獲取到某個鎖的時候,其餘線程沒法獲取到該鎖)編程
二、ReentrantLock 和 syschronized提供的相同的內存可見性(可見性:當一個線程修改一個變量的時候,其餘的線程可以及時知道)安全
三、雖然ReentrantLock命名爲可重入鎖,可是實際上synchronized也是同樣可重入的(可重入:當一個線程拿到某個鎖後,當該線程在釋放該鎖以前,能夠重複獲取該鎖)多線程
打個比方:好比你去食堂排隊打飯,飯桶只有一個飯勺。你手中拿着勺子,其餘人要打飯就得等你打完,這就叫互斥性(獨佔性),勺子就是鎖。你一開始左手拿着勺子,以爲姿式不爽,換成右手來拿勺子,此時勺子在你手裏,你能夠重複左手右手一個慢動做,這就叫可重入。當你打完飯的時候,飯桶裏的飯明顯少了不少(真的很能吃),其餘來打飯的人立刻就能看到了,這就叫可見性,飯桶是主存,你的飯盆就是工做內存。併發
首先最直觀的不一樣點,就是寫法上的不一樣。性能
1 Object object = new Object(); 2 synchronized (object) { 3 doSomeThing(s); 4 }
1 Lock lock=new ReentrantLock(); 2 lock.lock(); 3 try { 4 doSomeThing(s); 5 }finally { 6 lock.unlock(); 7 }
synchronized的寫法相比較更簡單,使用synchronized會自動加鎖,synchronized同步代碼塊退出後,會自動釋放鎖。測試
ReentrantLock須要在fiinally手動釋放鎖,若是忘記釋放鎖,會形成很大的麻煩。
synchronized必須寫在同一個代碼塊中,沒法進行拆分。ReentrantLock在能夠在不一樣的地方進行lock,unLock,代碼也能夠進行靈活編寫。好比concurrentHashMap中,Segment繼承ReentrantLock來進行鎖操做。
synchronized提供的功能相對單一,當須要對鎖進行復雜操做的時候,synchronized就會顯得力不從心。好比輪詢鎖、定時鎖、可中斷鎖。
第一種場景,假設有一段代碼同時須要獲取兩個鎖,當咱們使用synchronized的時候,會這麼寫
1 Object object1 = new Object(); 2 Object object2 = new Object(); 3 synchronized (object1) { 4 synchronized (object2){ 5 doSomeThing(object1,object2); 6 } 7 }
若是如今有另一段代碼,仍然須要同時鎖住object1和object2。synchronized就必須保證同步的順序,若是先鎖住object二、再鎖住object1,就有可能產生死鎖。當一段程序用到多個鎖,容易搞亂順序。特別在團隊開發的時候,成員之間不會知道他人經過什麼樣的順序進行加鎖。
這個時候ReentrantLock更加靈活的優勢就體現出來了。ReentrantLock提供了tryLock()方法,咱們能夠用這個方法來實現輪詢。
1 Lock lock1 = new ReentrantLock(); 2 Lock lock2 = new ReentrantLock(); 3 while (true) { 4 if (lock1.tryLock()) { 5 try { 6 if (lock2.tryLock()) { 7 try { 8 doSomeThing(s); 9 } finally { 10 lock2.unlock(); 11 } 12 } 13 } finally { 14 lock1.unlock(); 15 } 16 } 17 SECONDS.sleep(1); 18 }
使用ReentrantLock的tryLock(),如上述代碼所示,當咱們嘗試獲取lock1成功、獲取lock2失敗的時候,咱們會釋放lock1,休眠一秒後從新獲取兩個鎖。這樣的好處經過放棄已經獲取到的鎖避免了死鎖的出現,代碼的安全性更高。並且獲取鎖的過程當中,咱們能夠靈活的插入一些代碼,好比17行所示的,每當兩個鎖沒有同時獲取成功,咱們就進行一秒鐘休眠(雖然正常項目中下咱們不會這麼處理)。
第二種場景,若是咱們須要一個程序在特定時間內完成,若是特定時間內沒有拿到鎖,就直接返回,放棄該任務。synchronized面對這種場景一樣爲難,又到了ReentrantLock登場時間。reentrantLock的tryLock(long timeout, TimeUnit unit)方法提供了超時返回的功能。以下示例代碼:
1 Lock lock = new ReentrantLock(); 2 if(!lock.tryLock(1,SECONDS)){ 3 return; 4 } 5 try { 6 doSomeThing(); 7 }finally { 8 lock.unlock(); 9 }
上例中,ReentrantLock嘗試使用1秒的時候去獲取鎖,若是超過期間仍然沒有獲取到鎖,則返回失敗。若是獲取鎖成功,則繼續執行目的代碼。
在使用synchronized進行獲取鎖排隊的時候,是沒法響應中斷的,當業務場景必須實時響應線程中斷時,synchronized就不那麼合適了。使用ReantrantLock可使用lockInterruptibly()方法。該方法支持線程在獲取鎖的過程當中,對線程進行中斷。
1 Lock lock = new ReentrantLock(); 2 lock.lockInterruptibly(); 3 try { 4 doSomeThing(s); 5 } finally { 6 lock.unlock(); 7 }
在java 6.0以前,synchronized的性能方便比ReentrantLock弱一截,java 發佈6.0以後,從新優化了synchronized的算法。ReentrantLock的性能僅比synchronized的性能稍微強一些。至於如今,在不一樣系統,不一樣硬件下,測試出來的性能都會有誤差。這邊我也懶得本身再去作實驗嘗試,因此就很少逼逼了。
當多個線程前後請求一個被佔用的鎖的時候,當該鎖釋放,若是有算法保證最先排隊的線程最早拿到鎖,則這個鎖就是公平鎖。
synchronized是不公平,reentrantLock則同時提供了公平鎖和不公平鎖兩種。公平鎖的好處在於,線程的前後順序,等待最久的線程先執行。然而公平鎖的性能卻不如非公平鎖,緣由在於,假設線程A持有一個鎖,而且B線程請求這個鎖。因爲這個鎖已經被A線程持有,所以B將被掛起。當A釋放的鎖的時候,B將被喚醒,而後從新嘗試獲取該鎖。與此同時,若是C線程也同時請求該鎖,那麼C可能在B被徹底喚醒以前獲取該鎖、並執行任務、釋放鎖。即線程C可能在A、B線程持有鎖的間隔中,完成操做。B線程徹底喚醒以後會發現鎖已經被釋放了。所以並不影響B線程的執行。經過這種插隊行爲,能提升這個程序的吞吐量。
默認建立的ReentrantLock都是非公平的鎖,若是想建立一個公平鎖,能夠利用Reentrant的構造器。
//ReentrantLock 構造器
public ReentrantLock( boolean fair){ sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync(); }
Lock lock = new ReentrantLock(true);
如下簡單貼一下reentranLock的源碼,看下公平鎖和非公平鎖的區別:
1 static final class FairSync extends ReentrantLock.Sync { 2 3 4 //公平鎖,直接進行隊列 5 final void lock() { 6 acquire(1); 7 } 8 } 9 10 11 static final class NonfairSync extends ReentrantLock.Sync { 12 13 14 /** 15 * Performs lock. Try immediate barge, backing up to normal 16 * acquire on failure. 17 */ 18 //非公平鎖,能夠進行插隊 19 final void lock() { 20 if (compareAndSetState(0, 1)) 21 setExclusiveOwnerThread(Thread.currentThread()); 22 else 23 acquire(1); 24 } 25 26 protected final boolean tryAcquire(int acquires) { 27 return nonfairTryAcquire(acquires); 28 } 29 }
java語言規範並無要求JVM以公平的方式來實現內置鎖,各類JVM也確實沒有這麼作。ReentrantLock並無進一步下降鎖的公平性,在等待線程隊列中,依舊遵循先進先出的原則。
synchronized與ReentrantLock相比較以後,感受ReeantrantLock比synchronized有更強大的功能,更靈活的操做。可是相比起來,syschronzied使用門檻更低,簡單粗暴,並且不會出現忘記解鎖的情況。《Java併發編程實戰》中建議,在synchronized沒法知足需求的時候,再用ReeantrantLock,緣由大體以下:
一、synchronized簡單易用,容易上手,並且不容易出現忘記解鎖的狀況
二、將來更可能提升synchronized的性能,由於synchronized是JVM的內置屬性