日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

aqs原理初探以及公平鎖和非公平鎖實現

作者:huisheng_qaq 更新時間: 2022-07-12 編程語言

深入理解AQS

  • 一,AQS
    • 1,ReentrantLock
    • 2,CAS
    • 3,AbstractQueuedSynchronizer
      • 3.1,FairSync
      • 3.2,NofairSync
      • 3.3,AQS中幾個重要的相關參數
      • 3.4,Node

一,AQS

AbstractQueuedSynchronizer,定義了一套多線程訪問共享資源的同步器框架,依賴于狀態的同步器

1,ReentrantLock

一種基于AQS框架的應用實現,類似于synchronized是一種互斥鎖,可以保證線程安全。它支持手動加鎖與解鎖,支持加鎖的公平性。主要是Lock鎖的實現

public class ReentrantLock implements Lock, Serializable

接下來可以手動的猜想一下這個reentrantLock的實現

ReentrantLock lock = new ReentrantLock(true);
HashSet hashSet = new HashSet();
3個線程 T0,T1,T2
lock.lock(); //加鎖
	while(true){ //循環,輪詢獲取鎖
		if(加鎖成功){ //synchronized,cas  cas:compare and swap
			break;//跳出循環
		}
		//Thread.yeild() //讓出cpu使用權
		//Thread.sleep(1000); //睡眠
        hashSet.add(thread); //將當前線程加入到set中
		//阻塞
        LockSupprot.park();
	}
	T0獲取鎖
    xxxxx業務邏輯
    xxxxx業務邏輯   
lock.unlock();
Thread thread = hashSet.get();
//喚醒當前線程,notify是喚醒隨機的線程
LockSupport.unPark(thread);

三大核心:自旋,加鎖,LockSupport,隊列(LinkQueue),為了解決這個公平鎖和非公平鎖,因此優先考慮這個隊列。

2,CAS

compare and swap,比較與交換
在這里插入圖片描述

如在jmm模型中,兩個工作內存都去修改主內存的值。主內存中存在一個a = 0,線程A和線程B的工作內存同時獲取到這個值,如果線程A先修改這個值,則線程A會和主內存比較,如果線程A的值a和主內存的值一致,那么就會直接進行修改,如改成a = 1,那么線程B也要改這個值,線程B中的a = 0,那么會和主內存a比較,發現不一致,主內存a=1,那么就會優先將線程B中的值修改成a = 1,再對a進行修改。就是說相等直接修改,不相等需要重新讀取,再進行修改。即在一個原子操作里面進行比較和替換

主要通過這個unsafe類實現,里面的實現也是原子類操作,主要是通過以下三個類實現。

//對象類型
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//整型值
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//Long型值
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

3,AbstractQueuedSynchronizer

該類是ReentrantLock里面的一個抽象內部類。ctrl + alt + shift + u看所有子類,Ctrl + T,看所有的繼承類,可以發現很多地方都繼承或者實現了這個抽象類

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4dzYpLnW-1657115375965)(C:\Users\HULOUBO\AppData\Roaming\Typora\typora-user-images\1656947493769.png)]
Sync是ReentrantLock的一個抽象的靜態內部類,根據圖也可以發現這個Sync繼承了這個AQS

abstract static class Sync extends AbstractQueuedSynchronizer

通過實現Sync這個接口得到了FairSync公平鎖類和NofairSync非公平鎖這個類

3.1,FairSync

實現了公平鎖,需要排隊獲取鎖,如存在線程t1,t2,t3依次獲取鎖,需要依次排隊執行,突然來了一個線程t4,也是需要排在線程t3后面

ReentrantLock reentrantLock = new ReentrantLock(true);
public ReentrantLock(boolean fair) {
    //入參,用于判斷是公平鎖還是非公平鎖
    sync = fair ? new FairSync() : new NonfairSync();
}
//公平鎖的實現
static final class FairSync extends ReentrantLock.Sync{...}

公平鎖獲取鎖的方式如下

final void lock() {
    acquire(1);
}

public final void acquire(int arg) {
    //tryAcquire:嘗試去獲取鎖
    //addWaiter:線程入隊,一個同步等待隊列,基于雙向列表實現
    //鏈表中的每一個結點為一個Node結點,
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//線程入隊操作
private Node addWaiter(Node mode) {
    //獲取當前線程結點
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //結點入隊
    enq(node);
    return node;
}

//判斷是否獲取鎖
protected final boolean tryAcquire(int acquires) {
    //獲取當前獲取鎖的線程
    final Thread current = Thread.currentThread();
    //獲取當前同步器狀態,被volatile修飾的整型值,默認為0
    int c = getState();
    //如果同步狀態器的值為0,說明外面的線程可以進行加鎖操作
    if (c == 0) {
        //hasQueuedPredecessors:判斷隊列中是否還有在排隊的節點
        //compareAndSetState:原子操作,比較與交換,進行加鎖的操作,將state變量將0變為1
        //setExclusiveOwnerThread:設置獲取鎖的的線程擁有者
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果狀態同步器不為0,可能由自己持有,也可能由別的線程持有鎖
    //重復加鎖,如定義一個全局鎖,出現了這個可重入鎖的問題
    else if (current == getExclusiveOwnerThread()) {
        //如果自己持有鎖的話,state+1即可,反正不等于0就可以
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //別的線程獲有鎖,直接返回
    return false;
}

總而言之就是說,公平鎖就是就是通過一個隊列實現,需要進行排隊的去獲取鎖資源。主要是通過這個state的資源狀態器來控制獲取鎖的擁有者,如果state為0,則表示隊列中的下一個線程可以去獲取鎖,并且通過cas的方式來保證鎖的安全并發問題。通過隊列的思想,來保證這個獲取鎖的公平性和有序性。

3.2,NofairSync

實現了非公平鎖,默認為非公平鎖,會存在搶鎖的情況

ReentrantLock reentrantLock = new ReentrantLock(flase);
//如果不傳入參數,默認是非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    //入參,用于判斷是公平鎖還是非公平鎖
    sync = fair ? new FairSync() : new NonfairSync();
}
//非公平鎖的實現
static final class NonfairSync extends ReentrantLock.Sync{...}

非公平鎖獲取鎖的方法和公平鎖類似,只是少了幾步使用隊列的幾個方法

3.3,AQS中幾個重要的相關參數

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-12BMqIQA-1657115375967)(C:\Users\HULOUBO\AppData\Roaming\Typora\typora-user-images\1657029597278.png)]

exclusiveOwnerThread:用于記錄當前獨占模式下,獲取鎖的線程是誰

state:同步狀態器,默認為0,表示當前沒有線程獲取鎖,外面的線程可以來獲取鎖了

Node:雙向鏈表結構,是一個同步等待隊列,head:隊頭,tail:隊尾,prev前軀指針,next,后繼指針

waiteState:結點的什生命狀態

Lock鎖和synchronized鎖都是可重入鎖

3.4,Node

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Zfr5p0Ru-1657115375970)(C:\Users\HULOUBO\AppData\Roaming\Typora\typora-user-images\1657110491185.png)]
pre:前驅指針
next:后繼指針
waitStatues:每個結點都存在很多狀態,這個主要是存儲結點的生命狀態

結點的幾個生命狀態如下

SIGNAL:-1     //可被喚醒
CANCELLED:1	  //代表異常,中斷引起的,需要被廢棄
CONDITION:-2  //條件等待
PROPAGATE:-3  //傳播
Init:0初始狀態

結點入隊順序如下,入隊時,將入隊結點得前驅指針指向鏈表的tail結點,將tail節點的next節點指向當前節點,并將當前結點設置為tail尾指針結點。

private Node enq(final Node node) {
    //自旋
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            //如果隊列為空,要防止出現多個線程的并發問題,結點直接放在隊頭
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            //保證這個入隊的安全性,防止入隊出現并發問題
            //結點入隊到隊尾
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

當前結點前面有結點獲取鎖,當前節點需要進行阻塞park,線程要開始排隊等待。
結點在阻塞之前,還得嘗試獲取一次鎖。
a,如果結點可以獲取到鎖,即當前節點為頭結點的下一個結點,頭結點即將鎖被釋放,則把當前結點作為頭結點。之前的頭結點就可以被GC回收了
b,如果結點不能獲取到鎖,那么當前結點就要等待被喚醒
? (1),第一輪循環會去修改head狀態,并且將waitState修改為sinal = -1可被喚醒狀態
? (2),第二輪,阻塞線程

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //當前節點的前驅指針指向的節點
            final Node p = node.predecessor();
            //如果當前結點指向的該結點為頭部結點,則不需要進行阻塞
            if (p == head && tryAcquire(arg)) {
                //將當前結點作為頭部結點
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

每個結點的生命狀態的變換如下。默認的結點狀態為0,需要將節點狀態(waitState)轉化為-1可喚醒狀態。前一個節點中的waitStatus狀態記錄著后一個節點的生命狀態。

//pred:前驅節點 node:當前節點
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //默認為初始狀態0
    int ws = pred.waitStatus;
    //SIGNAL為-1,可被喚醒狀態
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //將頭結點初始狀態轉化為可喚醒狀態
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

在修改成可被喚醒的狀態之后,就進行阻塞操作。

private final boolean parkAndCheckInterrupt() {
    //阻塞線程,并且判斷該線程是否是被中斷的
    LockSupport.park(this);
    return Thread.interrupted();
}

在頭結點釋放鎖的時候,也會發一個通知去告知下一個需要獲取鎖的線程來搶鎖,即喚醒隊列中的下一個被阻塞的線程

//真正的釋放鎖
public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    //嘗試釋放鎖
    if (tryRelease(arg)) {
        Node h = head;
        //因為前一個結點中存放后一個節點中的聲明狀態,因此
        //如果頭結點的waitStatus不為0,就說明后一個結點是一個可被喚醒的狀態
        if (h != null && h.waitStatus != 0)
            //喚醒
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//嘗試去釋放鎖
protected final boolean tryRelease(int releases) {
    //修改同步狀態器state
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //將同步狀態器state 變為 0,說明當前同步狀態器可以進行鎖的獲取了
    if (c == 0) {
        free = true;
        //置空當前線程
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

最后在這個unparkSuccessor方法中,也有這個具體的unpark喚醒操作

if (s != null)
    LockSupport.unpark(s.thread)

通過上述代碼描述,也驗證了一開始的猜想:自旋,加鎖,LockSupport,隊列(LinkQueue)

?

原文鏈接:https://blog.csdn.net/zhenghuishengq/article/details/125648495

欄目分類
最近更新