AQS同步元件--ReentrantLock與鎖
ReentrantLock與鎖
Synchronized和ReentrantLock異同
- 可重入性:兩者都具有可重入性
- 鎖的實現:Synchronized是依賴jvm實現的,ReentrantLock是jdk實現的。(我們可以理解為一個是作業系統層面的實現另一個是使用者自己自己實現的)Synchronized的實現是jvm層面的很難看到其中的實現。而ReentrantLock是通過jvm實現的我們可以通過閱讀jvm原始碼來檢視實現。
- 效能區別:在Synchronized優化之前Synchronized的效能相比ReentrantLock差很多,在Synchronized引入了偏向鎖,輕量級鎖也就是自旋鎖之後了,兩者的效能相差不大了。在兩者都可用的情況下官方更推薦使用Synchronized,因為其寫法更簡單,Synchronized的優化就是借鑑了ReentrantLock中的cas技術。
- 功能區別:便利性,很明顯synchronized的使用更加便利,ReentrantLock在細粒度和靈活性中會優於Synchronized。
ReentrantLock獨有功能
- ReentrantLock可指定是公平鎖還是非公平鎖,Synchronized只能是非公平鎖。(公平鎖就是先等待的執行緒先獲得鎖)
- ReentrantLock提供一個Condition類,可以分組喚醒需要喚醒的形成。synchronized是要嘛隨機喚醒一個執行緒要嘛喚醒所有的執行緒。
- ReentrantLock提供了一種能夠中斷等待鎖的執行緒的機制lock.locInterruptibly(),ReentrantLock實現是一種自旋鎖通過迴圈呼叫,通過cas機制來實現加鎖。效能較好是因為避免了執行緒進入核心的阻塞狀態
@Slf4j public class LockExample2 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的執行緒數 public static int threadTotal = 200; public static int count = 0; private final static Lock lock = new ReentrantLock(); public static void main(String[] args) throws Exception { 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(); } } }
我們首先使用private final static Lock lock = new ReentrantLock()宣告一個所得例項,然後使用
lock.lock(); try { count++; } finally { lock.unlock(); }
進行加鎖和解鎖操作。
我們在通過一個例子來看看這個ReentrantReadWriteLock怎麼用。
@Slf4j public class LockExample3 { 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 { readLock.unlock(); } } class Data { } }
通過private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock()宣告一個ReentrantReadWriteLock,然後再分別獲取 private final Lock readLock = lock.readLock() private final Lock writeLock = lock.writeLock()讀鎖和寫鎖。
我們在這個map讀的時候加上讀鎖在寫的時候加上寫鎖,但是這裡有問題就是這個鎖是悲觀鎖,也就是說在執行寫鎖的時候一定不能有讀鎖,當讀操作特 特別多的時候很有可能會讓寫鎖一直無法執行。
我們看一下官方的例子學習一下,StampedLock
import java.util.concurrent.locks.StampedLock; public class LockExample4 { 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); //釋放讀鎖或寫鎖 } } } }
我們再將前面的裡改成StampedLock
@Slf4j public class LockExample5 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的執行緒數 public static int threadTotal = 200; public static int count = 0; private final static StampedLock lock = new StampedLock(); public static void main(String[] args) throws Exception { 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 stamp = lock.writeLock(); try { count++; } finally { lock.unlock(stamp); } } }
這裡和之前的不一樣的地方就是
long stamp = lock.writeLock(); try { count++; } finally { lock.unlock(stamp); }
在加鎖後會返回一個值,解鎖的時候需要傳入這個值。