並發之AQS原理(一)
如果說每一個同步的工具各有各的強大,那么這個強大背后是一個相同的動力,它就是AQS.
AQS是什么
AQS是指java.util.concurrent.locks包里的AbstractQueuedSynchronizer類,抽象的FIFO隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架
在了解AQS之前先了解下
先入先出隊列(First Input First Output,FIFO)這是一種傳統的按序執行方法,先進入的指令先完成並引退,跟着才執行第二條指令。
就是通常情況下的排隊。
AQS簡單的來說就是使用了一個共享變量來同步狀態,該狀態由子類去維護,而AQS框架做的是
- 線程阻塞隊列的維護。
- 線程阻塞和喚醒。
共享變量的操作通過Unsafe類提供的CAS操作完成的。AQS類主要使用的就是2個方法 acquire、release。
acquire(獲得鎖):返回true則放線程成繼續執行,否則將線程加入等待隊列中,等待喚醒.
release(釋放鎖):用於釋放鎖。釋放當前線程隊列中的頭結點,然后調用喚醒下一個結點的方法。
AQS的簡單使用
一般的來說AQS的使用方式是繼承,用的是模板方法模式。
下面我們來用一個簡單的AQS實現來逐步講解下AQS的原理。
EasyLock簡單實現AQS
首先實現一個簡單的鎖就叫EasyLock吧
/**
* 簡單不可重入鎖實現
*
*/
public class EasyLock extends AbstractQueuedSynchronizer {
/**
* 鎖定
*/
public void lock(){
acquire(1);
}
/**
* 嘗試鎖定
*/
public boolean tryLock(){
return tryAcquire(1);
}
/**
* 解鎖
*/
public void unlock(){
release(1);
}
/**
* 是否為鎖定
*/
public boolean isLocked(){
return isHeldExclusively();
}
/**
* 嘗試獲取鎖
*/
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 嘗試釋放鎖
*/
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
測試
public class AqsExampleDemo {
private static final int clientTotal = 30000;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
final EasyLock easyLock = new EasyLock();
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
easyLock.lock();
count++;
easyLock.unlock();
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("統計次數:" + count);
}
}
可以看出打印的結果是5000,並沒有出現並發問題。
總結
可以看出AQS的使用非常簡單幾乎只需要重寫 tryAcquire、tryRelease就可以自己實現一個鎖。
下一篇我們講解下AQS中底層的CLH隊列是如何保證其並發性的。