前言
對于安卓開發人員來說,AQS也是一個非常重要的知識點。如果把下面的知識記住了,那麼面試應該問題不大了。
一.模闆設計模式
我們平時感覺不到AQS(全程是
AbstractQueuedSynchronizer
)的存在,是因為AQS在使用的時候,一般是同步工具類的内部類繼承AQS,然後再在其他方法裡面封裝對這個内部類的操作。在學習AQS之前,我們必須要了解它的設計模式——模闆模式
關于模闆設計模式,我之前有過專門的講解。大家可以看我之前的一篇部落格
二.AQS
前言:關于Lock
這裡有一篇介紹Lock的文章,我推薦大家看一下。
我的了解就是:
Lock
是
synchronized
的改進版。這個觀點在某一篇部落格中有介紹,下面我把關于這部分的介紹截圖發出來。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5COwIjMyUTY1EmNlNzNyUDNzYzX0EDMyYTMxEzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
1.自定義獨占鎖(不可重入)
我們可以使用AQS自定義一個獨占鎖。首先,這個獨占鎖一定實作
Lock
接口。同時要實作
Lock
裡面的這些方法
然後我們定義内部類并繼承AQS,
private static class Sync extends AbstractQueuedSynchronizer {
/*判斷處于占用狀态*/
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
/*獲得鎖*/
@Override
protected boolean tryAcquire(int arg) {
//CAS操作
if(compareAndSetState(0,1)){
//儲存獲得鎖的線程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/*釋放鎖*/
@Override
protected boolean tryRelease(int arg) {
if(getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
//compareAndSetState(1,0);
return true;
}
// 傳回一個Condition,每個condition都包含了一個condition隊列
Condition newCondition() {
return new ConditionObject();
}
}
然後實作
Lock
所需要的方法
// 僅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
public void lock() {
System.out.println(Thread.currentThread().getName()+" ready get lock");
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+" already got lock");
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
System.out.println(Thread.currentThread().getName()+" ready release lock");
sync.release(1);
System.out.println(Thread.currentThread().getName()+" already released lock");
}
public Condition newCondition() {
return sync.newCondition();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
我們會發現所有的操作其實都是對
Sync
類的操作。
其中有一個小問題
就是獲得鎖的方法
lock
與解開鎖的方法
unlock
都是調用的
Sync
的
acquire
和
release
。但是我們在Sync 方法中并沒有找到這兩個方法。是以我們追蹤一下
acquire
方法看看
我們發現它調用了AQS的
tryAcquire
方法,而這個方法就是我們在内部類重寫的。這就合理了!
release
方法也是同理
完整代碼
public class SelfLock implements Lock {
// 靜态内部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer {
/*判斷處于占用狀态*/
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
/*獲得鎖*/
@Override
protected boolean tryAcquire(int arg) {
//CAS操作
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/*釋放鎖*/
@Override
protected boolean tryRelease(int arg) {
if(getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
//compareAndSetState(1,0);
return true;
}
// 傳回一個Condition,每個condition都包含了一個condition隊列
Condition newCondition() {
return new ConditionObject();
}
}
// 僅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
public void lock() {
System.out.println(Thread.currentThread().getName()+" ready get lock");
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+" already got lock");
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
System.out.println(Thread.currentThread().getName()+" ready release lock");
sync.release(1);
System.out.println(Thread.currentThread().getName()+" already released lock");
}
public Condition newCondition() {
return sync.newCondition();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
2.可重入鎖ReentrantLock
我們剛剛的自定義鎖其實原理已經和
ReentrantLock
很相似了。但是還是有一個很大的差別,就是可重入性。前面我們自定義的那個是不可重入的,而
ReentrantLock
是可重入的。
①什麼是可重入呢?
說一個應用場景吧,比如遞歸。在一個方法中,有類似這樣的邏輯
而在業務邏輯代碼中 會進行遞歸,也就是重新調用。這時候問題來了。我們之前是鎖住了的,是以在遞歸第一次的時候,會發現鎖已經被占用了(其實是被自己這個線程占用的)。是以對于可重入鎖來說,會判斷一下占用此鎖的線程是不是就是自己,如果是的話,就皆大歡喜,将某一個辨別符+1表示自己又占了一次鎖,然後就可以繼續操作。如果不是自己占用的就進入等待隊列,等待鎖被釋放。
是以在這裡可重入鎖就是你可以對一個
ReentrantLock
對象多次執行
lock()
加鎖和
unlock()
釋放鎖,也就是可以對一個鎖加多次,叫做可重入加鎖。
②state
到這裡可重入性大家就了解了。那麼還有一個問題,就是剛剛說到的辨別符。其實就是AQS裡面一個非常非常重要的成員變量
state
。
當沒有線程占用鎖的時候,
state
為0.當有線程占用的時候,
state≥1
.當某一個線程隻占用了一次的時候,
state
為1,當某一個線程重入了多次的時候,
state>1
.
③那麼具體如何來實作可重入性呢?别急,往下看
我們把剛剛的例子改為可重入的,隻需要更改内部類就可以
// 靜态内部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer {
//*********************
// 不同點1:是否處于占用狀态
//*********************
protected boolean isHeldExclusively() {
return getState() > 0;
}
// 當狀态為0的時候擷取鎖
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}else if(getExclusiveOwnerThread()==Thread.currentThread()){
//*********************
// 不同點2:如果是本線程占用了鎖,那麼将再占用一次,并且state+1
//*********************
setState(getState()+1);
return true;
}
return false;
}
// 釋放鎖,将狀态設定為0
protected boolean tryRelease(int releases) {
//*********************
// 小不同點:異常判斷
//*********************
if(getExclusiveOwnerThread()!=Thread.currentThread()){
throw new IllegalMonitorStateException();
}
if (getState() == 0)
throw new IllegalMonitorStateException();
//*********************
// 不同點3:釋放的時候,每一次釋放都将state-1,當減到0時說明鎖已經沒有被占用了,則設定占用鎖的線程為null
//*********************
setState(getState()-1);
if(getState()==0){
setExclusiveOwnerThread(null);
}
return true;
}
// 傳回一個Condition,每個condition都包含了一個condition隊列
Condition newCondition() {
return new ConditionObject();
}
}
3.AQS核心思想之CLH隊列原理
CLH隊列由連結清單實作,每一個線程被封裝成一個Node結點。除此之外還有兩個屬性,即
myPred
和
locked
。
myPred
指向每一個結點的前驅節點,
locked
為
true
表明這個節點還需要鎖,一旦
locked
變為
false
,則說明這個節點已經使用完鎖并且将其釋放掉了。
那麼具體怎麼看前面的節點的線程已經釋放掉鎖了呢?就是通過不斷自旋的方式,即某一線程在前驅結點的
locked
字段上旋轉
比如
QNode_A
的
locked
由
true
變成了
false
,那麼
QNode_B
的
myPred
在某一次自旋後發現了這個變化,則說明前面線程已經使用完鎖了,該輪到我
QNode_B
節點來用鎖了。
這就是CLH隊列的基本思想。AQS就是基于這個思想來的,當然,在具體實作方面對CLH做了一些改進。比如使用雙向連結清單,而且不會無限次自旋,而是詢問一定次數後就将線程進行阻塞,當鎖被釋放的時候就會喚醒阻塞的線程。雖說如此,但核心思想還是與CLH保持一緻的。
sunchronized
實作的基本思想也是CLH隊列鎖。
公平鎖與非公平鎖
以上也是一個公平鎖的概念。就是每一個來的線程都得去排隊,誰也不能例外。但是還有一種是非公平鎖,顧名思義就是每一個新來的線程可以不用排隊直接搶占鎖。
其實公平鎖和非公平鎖的實作是非常相似的
唯一的一處不同是在
tryAcquire
這個方法
先看公平鎖
/**
* Acquires only if thread is first waiter or empty
*/
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
再看非公平鎖
/**
* Acquire for non-reentrant cases after initialTryLock prescreen
*/
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}