Java中主要分爲兩類鎖,一類是synchronized修飾的鎖,另一類就是J.U.C中提供的鎖。J.U.C中提供的核心鎖就是ReentrantLock。java
(1)可重入性
兩者都是同一個線程進入1次,鎖的計數器就自增1,須要等到鎖的計數器降低爲0時,才能釋放鎖。markdown
(2)鎖的實現
synchronized是基於JVM實現的,而ReentrantLock是JDK實現的。多線程
(3)性能的區別
synchronized優化以前性能比ReentrantLock差不少,可是自從synchronized引入了偏向鎖,輕量級鎖也就是自旋鎖後,性能就差很少了。併發
(4)功能區別ide
synchronized使用起來比較方便,而且由編譯器保證加鎖和釋放鎖;ReentrantLock須要手工聲明加鎖和釋放鎖,最好是在finally代碼塊中聲明釋放鎖。工具
在這點上ReentrantLock會優於synchronized。性能
ReentrantLock可指定是公平鎖仍是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先得到鎖。優化
提供了一個Condition類,能夠分組喚醒須要喚醒的線程。而synchronized只能隨機喚醒一個線程,或者喚醒所有的線程ui
提供可以中斷等待鎖的線程的機制,lock.lockInterruptibly()。ReentrantLock實現是一種自旋鎖,經過循環調用CAS操做來實現加鎖,性能上比較好是由於避免了使線程進入內核態的阻塞狀態。atom
synchronized能作的事情ReentrantLock都能作,而ReentrantLock有些能作的事情,synchronized不能作。
在性能上,ReentrantLock不會比synchronized差。
不用手動釋放鎖,JVM自動處理,若是出現異常,JVM也會自動釋放鎖。
JVM用synchronized進行管理鎖定請求和釋放時,JVM在生成線程轉儲時可以鎖定信息,這些對調試很是有價值,由於它們能標識死鎖或者其餘異常行爲的來源。而ReentrantLock只是普通的類,JVM不知道具體哪一個線程擁有lock對象。
synchronized能夠在全部JVM版本中工做,ReentrantLock在某些1.5以前版本的JVM中可能不支持。
boolean tryLock():僅在調用時鎖定未被另外一個線程保持的狀況下才獲取鎖定。
boolean tryLock(long, TimeUnit): 若是鎖定在給定的等待時間內沒有被另外一個線程保持,且當前線程沒有被中斷,則獲取這個鎖定。
void lockInterruptibly():若是當前線程沒有被中斷,就獲取鎖定;若是被中斷,就拋出異常。
boolean isLocked():查詢此鎖定是否由任意線程保持。
boolean isHeldByCurrentThread(): 查詢當前線程是否保持鎖定狀態。
boolean isFair():判斷是不是公平鎖。
boolean hasQueuedThread(Thread):查詢指定線程是否在等待獲取此鎖定。
boolean hasQueuedThreads():查詢是否有線程正在等待獲取此鎖定。
boolean getHoldCount():查詢當前線程保持鎖定的個數。
示例代碼以下:
package io.binghe.concurrency.example.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @Slf4j public class LockExample { //請求總數 public static int clientTotal = 5000; //同時併發執行的線程數 public static int threadTotal = 200; public static int count = 0; private static final Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); add(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); } private static void add(){ lock.lock(); try{ count ++; }finally { lock.unlock(); } } }
在沒有任何讀寫鎖的時候,才能夠取得寫鎖。若是一直有讀鎖存在,則沒法執行寫鎖,這就會致使寫鎖飢餓。
示例代碼以下:
package io.binghe.concurrency.example.lock; import lombok.extern.slf4j.Slf4j; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @Slf4j public class LockExample { private final Map<String, Data> map = new TreeMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public Data get(String key){ readLock.lock(); try{ return map.get(key); }finally { readLock.unlock(); } } public Set<String> getAllKeys(){ readLock.lock(); try{ return map.keySet(); }finally { readLock.unlock(); } } public Data put(String key, Data value){ writeLock.lock(); try{ return map.put(key, value); }finally { writeLock.unlock(); } } class Data{ } }
控制鎖三種模式:寫、讀、樂觀讀。
StampedLock的狀態由版本和模式兩個部分組成,鎖獲取方法返回的是一個數字做爲票據,用相應的鎖狀態來表示並控制相關的訪問,數字0表示沒有寫鎖被受權訪問。
在讀鎖上分爲悲觀鎖和樂觀鎖,樂觀讀就是在讀操做不少,寫操做不多的狀況下,能夠樂觀的認爲寫入和讀取同時發生的概率很小。所以,不悲觀的使用徹底的讀取鎖定。程序能夠查看讀取資料以後,是否遭到寫入進行了變動,再採起後續的措施,這樣的改進能夠大幅度提高程序的吞吐量。
總之,在讀線程愈來愈多的場景下,StampedLock大幅度提高了程序的吞吐量。
StampedLock源碼中的案例以下,這裏加上了註釋。
class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } //下面看看樂觀讀鎖案例 double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); //得到一個樂觀讀鎖 double currentX = x, currentY = y; //將兩個字段讀入本地局部變量 if (!sl.validate(stamp)) { //檢查發出樂觀讀鎖後同時是否有其餘寫鎖發生? stamp = sl.readLock(); //若是沒有,咱們再次得到一個讀悲觀鎖 try { currentX = x; // 將兩個字段讀入本地局部變量 currentY = y; // 將兩個字段讀入本地局部變量 } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } //下面是悲觀讀鎖案例 void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { //循環,檢查當前狀態是否符合 long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉爲寫鎖 if (ws != 0L) { //這是確認轉爲寫鎖是否成功 stamp = ws; //若是成功 替換票據 x = newX; //進行狀態改變 y = newY; //進行狀態改變 break; } else { //若是不能成功轉換爲寫鎖 sl.unlockRead(stamp); //咱們顯式釋放讀鎖 stamp = sl.writeLock(); //顯式直接進行寫鎖 而後再經過循環再試 } } } finally { sl.unlock(stamp); //釋放讀鎖或寫鎖 } } }
示例代碼以下:
package io.binghe.concurrency.example.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.StampedLock; @Slf4j public class LockExample { //請求總數 public static int clientTotal = 5000; //同時併發執行的線程數 public static int threadTotal = 200; public static int count = 0; private static final StampedLock lock = new StampedLock(); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); add(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); } private static void add(){ //加鎖時返回一個long類型的票據 long stamp = lock.writeLock(); try{ count ++; }finally { //釋放鎖的時候帶上加鎖時返回的票據 lock.unlock(stamp); } } }
咱們能夠這樣選擇使用synchronozed鎖仍是ReentrantLock鎖:
當只有少許競爭者時,synchronized是一個很好的通用鎖實現
競爭者很多,可是線程的增加趨勢是可預估的,此時,ReentrantLock是一個很好的通用鎖實現
synchronized不會引起死鎖,其餘的鎖使用不當可能會引起死鎖。
Condition是一個多線程間協調通訊的工具類,Condition除了實現wait和notify的功能之外,它的好處在於一個lock能夠建立多個Condition,能夠選擇性的通知wait的線程
特色:
Condition 的前提是Lock,由AQS中newCondition()方法 建立Condition的對象
Condition await方法表示線程從AQS中移除,並釋放線程獲取的鎖,並進入Condition等待隊列中等待,等待被signal
Condition signal方法表示喚醒對應Condition等待隊列中的線程節點,並加入AQS中,準備去獲取鎖。
示例代碼以下
package io.binghe.concurrency.example.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @Slf4j public class LockExample { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Condition condition = reentrantLock.newCondition(); new Thread(() -> { try { reentrantLock.lock(); log.info("wait signal"); // 1 condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } log.info("get signal"); // 4 reentrantLock.unlock(); }).start(); new Thread(() -> { reentrantLock.lock(); log.info("get lock"); // 2 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } condition.signalAll(); log.info("send signal ~ "); // 3 reentrantLock.unlock(); }).start(); } }