目錄
四、AbstractQueuedSynchronizer(AQS)
一、 可重入鎖與非可重入鎖
可重入鎖,就是當一個佔有鎖的線程再次進入到這個鎖的代碼塊時,可以直接獲得鎖並且執行,而無需等待。
與之相對的,就是非可重入鎖,當一個佔有鎖的線程再次進入到這個鎖塊時仍然需要等待獲取鎖。
Java中的synchronized和ReentrantLock都是可重入鎖。
可重入鎖的優點是:同一個線程獲取同一個鎖時不會造成死鎖,且加快了執行的速度。
我們舉一個例子來看下可重入鎖:
public class Main {
public static void funcA(){
synchronized (Main.class){ //加鎖
System.out.println("a");
funcB();
System.out.println("done");
}
}
public static void funcB(){
synchronized (Main.class){ //加鎖,同一把鎖
System.out.println("b");
}
}
public static void main(String[] args) {
funcA();
}
}
上面的代碼,funcA與funcB中加的是同一把鎖,funcA先獲得了鎖開始執行。之後funcA中調用了funcB,funcB同樣也加了鎖,如果鎖不可重入,就會造成死鎖,致使後面的信息打印不出來。而synchronized是可重入鎖,避免了死鎖。
執行結果:
二、 公平鎖與非公平鎖
公平鎖:獲取鎖的機會按照嚴格的請求時間先後順序,有個先來後到。
非公平鎖:不按照先後順序,誰來了都可以tryAcquire試一下,誰能成功全靠運氣。
公平鎖與非公平鎖的實現原理:
將需要獲取鎖的線程加入到一個FIFO的隊列中,按照先進先出的原則,每次都是排在第一個的線程擁有獲得鎖的機會。
公平鎖與非公平鎖的比較: 哪一個更好一點呢?
公平鎖比非公平鎖多用了一個隊列,耗費了一點點內存,但是它可以使所有需要獲取鎖的線程在排隊的時候處於等待狀態(除了排在隊頭的可以處於running),CPU輪詢的時候就無需對這些處於等待狀態的線程進行調度,節省了CPU資源。因此,公平鎖更好一些。
三、獨佔鎖與共享鎖
獨佔鎖,就是一個線程獲取鎖之後,在其釋放鎖前,其他線程就不能獲取這個鎖;
共享鎖,就是一個線程獲取鎖之後,其他的線程也可以獲取鎖並執行同步代碼塊。
ReentrantReadWriteLock ,它的寫鎖就是一個獨佔鎖,它的讀鎖就是一個共享鎖。
- 當上了寫鎖之後,就只允許這一個線程寫,其他線程就不能讀不能寫了;
- 而當只有讀鎖的時候,多個線程可以共同讀取數據,因爲只讀的時候不會產生線程安全性問題。
四、AbstractQueuedSynchronizer(AQS)
AbstractQueuedSynchronizer內部維護了一個FIFO的隊列和一個 int 狀態變量,隊列用來實現排隊,狀態變量用來控制是否可以獲得鎖。 它爲實現依賴於FIFO隊列的阻塞鎖和相關同步器提供了一個框架。
AQS實現阻塞鎖通常是通過子類extends AQS來實現的。
AQS定義了很多定義爲final的方法,用來提供服務。
當子類繼承AQS之後,還有幾個方法需要自己實現,用來完成鎖的獲取和釋放。
tryAcquire(int arg) | 嘗試獲取鎖(獨佔) |
tryRelease(int arg) | 嘗試釋放鎖(獨佔) |
tryAcquireShared(int arg) | 嘗試獲取鎖(共享) |
tryReleaseShared(int arg) | 嘗試釋放鎖(共享) |
isHeldExclusively | 返回boolean, 是否被獨佔 |
如果是實現獨佔鎖,就實現前兩個方法;
如果是實現共享鎖,就實現後兩個方法。
這些個方法的原始寫法只是拋出一個異常,需要繼承者自己實現。
AQS中的其他方法及其作用下面以ReentrantLock的實現爲例來講解。
五、ReentrantLock
ReentrantLock是通過繼承AQS來實現的,它的類結構圖是這樣
ReentrantLock中包含了一個Sync類,Sync類繼承了AQS的諸多方法。
NonFairSync 和 FairSync繼承了Sync類,它們兩個是真正用來幹活的,它們各有兩個方法 lock 和 tryAcquire用來實現獲取鎖,FairSync實現公平鎖,NonFairSync實現非公平鎖。
看一下ReentrantLock的源碼實現
1. lock方法
非公平鎖先嚐試去獨佔,如果不成功再和公平鎖一樣調用AQS的acquire()方法。在acquire()方法中,調用了addWaiter,正是addWaiter方法中將想要獲取鎖的線程加入了FIFO的Queue中。
2. tryAcquire方法
非公平鎖直接調用Sync類的nonfairTryAcquire方法,其實nonfairTryAcquire的實現與公平鎖的tryAcquire差不多,只是多加了一個判斷線程隊列是否爲空的步驟而已。
如果線程隊列不爲空又不是同一個線程重入,則直接return false,會使處於Queue的頭節點的線程優先獲取鎖,從而實現獲取時間上的公平。
3. 可重入的實現
在tryAcquire方法中,else if 判斷如果當前線程是已經獨佔了鎖的線程,那麼只是將鎖的層數+1,然後返回true,就相當於又獲取了鎖,從而實現了可重入。
六、用AQS實現一個自己寫的重入鎖
import javax.management.RuntimeErrorException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock{ //implements Lock,聲明MyLock爲鎖
private static Helper helper = new Helper();
private static class Helper extends AbstractQueuedSynchronizer{ //同Sync一樣extends AQS實現功能
@Override
protected boolean tryAcquire(int arg) {
int state = getState();
if(0 == state){
if(compareAndSetState(arg)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
//實現可重入
else if(getExclusiveOwnerThread() == Thread.currentThread()){
setState(state + arg);
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
//容錯處理
if (getExclusiveOwnerThread() != Thread.currentThread()){
throw new RuntimeException();
}
int state = getState() - arg;
if (state == 0){
setExclusiveOwnerThread(null);
setState(state); //這裏還有下面都有setState(state)但不能合併提前到前面,否則先setState(state)就會造成釋放另一個線程先運行,造成原子性失敗
return true;
}
setState(state); //setState(state)不能合併提前到前面
return false;
}
@Override
protected boolean isHeldExclusively() {
return 0 != getState();
}
protected Condition getCondition(){
return new ConditionObject();
}
}
@Override
public void lock() {
helper.acquire(1);
}
@Override
public void unlock() {
helper.release(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
helper.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return helper.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return helper.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public Condition newCondition() {
return helper.getCondition();
}
}
測試代碼
public class Main {
private static volatile int num = 0;
private static MyLock lock = new MyLock();
public static void main(String[] args) {
//起兩個線程交替打印確定不會出現線程安全性問題
new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(lock.tryLock()){
++num;
System.out.println(Thread.currentThread().getName() + " " + num);
lock.unlock();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(lock.tryLock()){
System.out.println(Thread.currentThread().getName() + " " + num);
++num;
lock.unlock();
}
}
}
}).start();
}