# AQS
隊列同步器AbstractQueuedSynchronizer,用來構建鎖或者其他同步組件的基礎框架,內部使用一個變量state來表示同步狀態,同時使用一個FIFO隊列來完成線程的排隊工作。
鎖或者其他同步組件一般都會定義一個靜態內部類,該靜態內部類會繼承AQS,同時**重寫**AQS中的方法,重寫AQS中的方法時需要用到下面三個方法來獲取同步狀態。
- **getState()**
獲取state屬性的內容。
- **setState(int newState)**
設置state屬性的內容。
- **compareAndSetState(int expect, int update)**
使用CAS設置當前狀態,保證狀態設置的原子性。
> 總結:如何自定義一個鎖或者同步組件?
> 創建靜態內部類繼承AQS,重寫AQS中的**可重寫**的方法,在里面使用AQS提供的如上三個方法來獲取、修改同步狀態。最后調用AQS中的模板方法來進行操作,模板方法中會調用重寫的方法。
> **即使用者調用模板方法,模板方法調用重寫方法,重寫方法調用如上三個方法。**
:-: 
## 可重寫的方法
1.
```
protected boolean tryAcquire(int arg);
```
**獨占式獲取**同步狀態,查詢當前狀態并根據具體條件設置同步狀態。
2.
```
protected boolean tryRelease(int arg);
```
**獨占式釋放**同步狀態,等待的線程有機會獲取同步狀態。
3.
```
protected int tryAcquireShared(int arg);
```
**共享式獲取**同步狀態,返回大于等于0的值表示獲取成功,反之獲取失敗。
4.
```
protected boolean tryReleaseShared(int arg);
```
**共享式釋放**同步狀態。
5.
```
protected boolean isHeldExclusively();
```
表示是否被當前線程占用。
## 模板方法
1. 獨占式獲取同步狀態
```
void acquire(int arg);
```
當前線程獲取成功則會返回,否則進入同步隊列**等待**,調用重寫方法中的**tryAcquire**。
2. 獨占式獲取同步狀態,響應中斷
```
void acquireInterruptible(int arg);
```
如果當前線程被中斷,則會拋出InterruptedException。
3. 超時獲取同步狀態
```
boolean tryAcquireNanos(int arg, long nanos);
```
在acquireInterruptible的基礎上設置超時時間,如果超時時間還沒有獲取到同步狀態,會返回false,否則返回true。
4. 共享獲取同步狀態
```
void acquireShared(int arg);
```
5. 共享獲取同步狀態,響應中斷
```
void acquireSharedInterruptible(int arg);
```
6. 共享獲取同步狀態,響應中斷,添加超時時間
```
boolean tryAcquireSharedNanos(int arg, long nanos);
```
7. 獨占式釋放同步狀態
```
boolean release(int arg);
```
同步隊列中的第一個節點將會被喚醒。
8. 共享式釋放同步狀態
```
boolean releaseShared(int arg);
```
9. 獲取等待在同步隊列上的線程集合
```
Collection<Thread> getQueuedThreads();
```
總之:模板可以分為三類:獨占式獲取與釋放同步狀態、共享式獲取與釋放同步狀態、查詢同步隊列線程等待情況。獲取又有分為`中斷`、`超時`。
## 自定義同步組件
~~~
/**
* 自定義不可重入鎖
*/
public class UnReetrantLock implements Lock {
// 同步鎖
public static class Sync extends AbstractQueuedSynchronizer {
/**
* 嘗試獨占式獲取鎖
*
* @param arg
* @return
*/
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) { // 調用CAS,當state為0時獲取成功,底層由C++提供
// 加上鎖了
// 設置持有鎖的線程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 嘗試釋放鎖
*
* @param arg
* @return
*/
@Override
protected boolean tryRelease(int arg) {
// 不會有其他線程競爭、直接設置就行了
setExclusiveOwnerThread(null);
setState(0); // 保證前面的內容對其他線程可見
return false;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
~~~
## AQS實現
**底層數據結構:同步隊列**
AQS中使用一個雙向鏈表來保存等待同步狀態的線程,鏈表的節點用其內部自定義的Node表示,Node類源碼:
~~~
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 同步狀態
volatile int waitStatus;
volatile Node prev;
volatile Node next;
// 線程引用
volatile Thread thread;
Node nextWaiter;
}
~~~
waitStatus有五個狀態:
- cancelled = 1:同步隊列中的線程等待超時或者中斷時的狀態,后續不會再改變。
- signal = -1:節點獲取同步狀態,一般是隊頭節點,后續節點處于等待狀態。
- condition = -2:節點在等待隊列中(注意不是同步隊列),線程等待Condition,當Condition調用了signal()之后,該節點會從**等待隊列**轉移到**同步隊列**。
- propagate = -3:
- initial = 0:初始狀態。
同步隊列采用尾插法的方式,同時會使用CAS保證尾插的時候是線程安全的。其結構如下:
:-: 
其中隊頭是獲取同步狀態成功的節點,當首節點的線程釋放同步狀態的時候,會喚醒后繼的節點,后繼節點會成為首節點。(這個過程不用CAS,沒有競爭的情況。)
**acquire方法流程**
~~~
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
~~~
:-: 
同步隊列中的節點不斷的在自旋判斷其**前驅節點是不是頭節點**,如果是則嘗試獲取同步狀態,否則會阻塞節點中的線程。
**acquireShared方法流程**
~~~
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
~~~
:-: 
## ReetantLock
ReentrantLock,支持重入鎖和公平與非公平鎖。
### ReentrantLock實現可重入
重入鎖:支持線程反復的獲取鎖資源而不會自己阻塞自己,有兩個問題要實現:
1. 線程再次獲取鎖,判斷是否是當前線程獲取鎖。
2. 鎖的最終釋放,需要計數鎖被重入幾次,計數器最終釋放為0時才表示鎖的最終釋放。
:-: 
例如非公平鎖每次再嘗試獲取鎖的時候都會判斷是不是同個線程,如果是的話增加計數器的值。釋放鎖時等到計數器的值為0時才將占有鎖的線程設置為null。
:-: 
### 公平鎖與非公平鎖
公平鎖:獲取鎖的線程按照絕對的時間順序,FIFO。
非公平鎖:只要CAS設置同步狀態成功,就獲取鎖,不會按照FIFO順序。
ReentrantLock的構造方法中傳入true時可以創建公平鎖:
~~~
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
~~~
公平鎖在tryAcquire的時候會判斷當前線程是否有前驅節點,有的話則會等待前驅節點釋放之后在獲取嘗試獲取鎖。
公平鎖的tryAcquire:
:-: 
hasQueuePredecessors方法用來判斷是否有前驅節點
非公平鎖的tryAcquire:
:-: 
> 問:如何實現公平鎖?
> 構造函數的參數傳入true,在重寫的tryAcquire方法中判斷當前線程是否有前驅線程,有的話嘗試獲取同步狀態失敗,以此來達到公平的效果。
對比:
公平鎖雖然會按照FIFO原則,但是會進行大量的線程切換,非公平鎖雖然可能會造成其他線程饑餓,但是可以極大提高吞吐量。
:-: 
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper