1. 程式人生 > 其它 >Go語言圖片處理和生成縮圖的方法

Go語言圖片處理和生成縮圖的方法


ReentrantLock原始碼分析

Author:鄭金維

一、ReentrantLock介紹

ReentrantLock,synchronized

synchronized同步鎖,鎖升級,但是synchronized只有升級,沒有降級,鎖競爭激烈,不推薦synchronized

ReentrantLock同步鎖,內部是基於CAS和AQS實現的,在鎖競爭比較激烈時,推薦使用ReentrantLock

ReentrantLock基本使用(可重入鎖):

獲取鎖:ReentrantLock物件.lock();

釋放鎖:ReentrantLock物件.unlock();

二、CAS、AQS掃盲

2.1 CAS(樂觀鎖實現方式)

Compare And Swap,是針對一個值進行修改(原子性)

在Java中提供了Unsafe類實現了CAS的操作。

ABA問題:新增版本號。

CAS自旋次數過多:

  • synchronized基於自適應自旋鎖解決。
  • LongAdder基於Cell[],在CAS實現失敗,將資料扔到Cell[]中,後期再新增

劣勢:CAS只能保證修改一個值的時候,是原子性的。CAS無法直接鎖住一段程式碼。

2.2 AQS

AQS是什麼:就是Java中的一個類,名字:AbstractQueuedSynchronizer

AQS是JUC中的一個基礎類(併發基礎類),很多其他類都是基於AQS實現的:

  • ReentrantLock
  • CountDownLatch
  • 訊號量
  • 阻塞佇列~
  • 執行緒池的Worker類

AQS中維護著一個非常重要的屬性:state

還維護著一個雙向佇列:Node類

三、加鎖原始碼分析

3.1 加鎖流程

如果線上程B被喚醒之後:

  • 非公平鎖,執行緒B會和其他執行緒一起競爭鎖資源
  • 公平鎖,其他執行緒需要先到AQS排隊

3.2 lock原始碼

實現方式就是在ReentrantLock的有參構建中追加,預設false為非公平,true為公平

非公平鎖:

final void lock() {
	// 先搶一波鎖。
    if (compareAndSetState(0, 1))
		// 如果成功的將state從0改為1,代表拿到鎖資源,那就將exclusiveOwnerThread設定為當前執行緒
        setExclusiveOwnerThread(Thread.currentThread());
    else
		// 沒搶到,執行acquire方法
        acquire(1);
}

公平鎖

final void lock() {
	// 執行acquire方法
    acquire(1);
}

ReentrantLock競爭鎖資源就是以CAS的方式,嘗試將state從0改為1,修改成功,獲取鎖資源成功

3.3 acquire原始碼

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
3.3.1 tryAcquire方法

非公平鎖:

// 非公平鎖!
final boolean nonfairTryAcquire(int acquires) {
    // 拿到當前執行緒~
    final Thread current = Thread.currentThread();
    // 獲取state
    int c = getState();
    // 如果state為0,就再次以CAS的方式嘗試拿到鎖資源
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 當前是鎖重入操作!將state+1,即為重入ok,同時釋放鎖也需要釋放多次,釋放鎖是對state - 1
    else if (current == getExclusiveOwnerThread()) {
        // state + 1
        int nextc = c + acquires;
        if (nextc < 0) // overflow      2進位制的符號位,加了1,代表是負數。代表超過鎖重入執行緒
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        // 鎖重入成功,返回true
        return true;
    }
    return false;
}

公平鎖跟上述程式碼基本一致,有一個區別,當判斷c == 0成功後

  • 公平鎖需要先嚐試排隊執行hasQueuedPredecessors()方法,如果需要排隊,就排隊去
  • 如果不需要,那就直接CAS獲取鎖資源
3.3.2 addWaiter方法

將當前沒有獲取到鎖資源的執行緒,封裝為Node,開始排隊

// 將當前沒有獲取到鎖資源的執行緒,封裝為Node,開始排隊
private Node addWaiter(Node mode) {
    // 將當前執行緒封裝為Node
    Node node = new Node(Thread.currentThread(), mode);
    // 獲取了tail
    Node pred = tail;
    // 如果tail不為null,說明佇列中有節點!
    if (pred != null) {
        // 當前Node的上一個是之前的tail節點
        node.prev = pred;
        // 將tail指向當前節點
        if (compareAndSetTail(pred, node)) {
            // 將pred的next節點,指向當前節點
            pred.next = node;
            return node;
        }
    }
    // 證明tail指向null,說明佇列沒Node
    enq(node);
    // 返回當前節點
    return node;
}
private Node enq(final Node node) {
    // 死迴圈
    for (;;) {
        // 重新獲取tail
        Node t = tail;
        if (t == null) { 
            // tail還是指向null(第一個來排隊的)
            // 設定一個空節點作為head和tail
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // tail不是null,說明有併發操作,並且有人排到了我的前面
            node.prev = t;
            // 跟之前操作一樣,插入到佇列尾巴
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
3.3.3 acquireQueued方法

如果當前節點的上一個節點是head,嘗試獲取鎖資源,如果不是方法會將當前執行緒進行掛起。

final boolean acquireQueued(final Node node, int arg) {
    // 宣告變數,
    boolean failed = true;
    for (;;) {
        // 拿到當前節點的prev節點,命名為p
        final Node p = node.predecessor();
        // p是head頭節點,當前節點就再次嘗試獲取鎖資源
        if (p == head && tryAcquire(arg)) {
            // 當前節點獲取鎖資源成功,設定當前節點為head,之前head等待GC回收
            setHead(node);
            p.next = null;
            failed = false;
            return interrupted;
        }
        // 檢視是否需要掛起當前執行緒,如果可以掛起
        if (shouldParkAfterFailedAcquire(p, node) &&
            // 掛起執行緒,執行了LockSupport.park()掛起執行緒
            parkAndCheckInterrupt())
    }
}
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

waitStatus:new的Node預設值為0
-1: 正常
1:涼涼了
-2,-3:不正常,但是可以改~~~
// 判斷當前執行緒的上一個節點的狀態是不是-1
// 如果是-1,當前執行緒可以阻塞
// 如果是1,就往前找,找狀態為-1的
// 如果不是-1,也不是1,那就上一個節點狀態設定為-1
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == -)
        return true;
    if (ws == 1) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

四、釋放鎖原始碼分析(白給的)

將state - 1,如果減完之後,state是0,unpark喚醒正在排隊,並且離head最近的執行緒

public void unlock() {
    // 公平和非公平都走當前方法
    sync.release(1);
}
public final boolean release(int arg) {
    // 嘗試釋放
    if (tryRelease(arg)) {
        // 鎖釋放成功
        // 拿到head
        Node h = head;
        // 排隊的佇列中,有head,並且頭的狀態正常!!!
        if (h != null && h.waitStatus != 0)
            // 喚醒掛起的執行緒!
            unparkSuccessor(h);
        return true;
    }
    // 釋放失敗,還得釋放幾次~~~~
    return false;
}
// 針對可重入鎖的操作。
protected final boolean tryRelease(int releases) {
    // 拿state的值進行-1後的值,賦值給c
    int c = getState() - releases;
    // 如果釋放鎖的執行緒不是擁有鎖的執行緒,丟擲異常。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 標識
    boolean free = false;
    if (c == 0) {
        // 如果state-1之後為0,代表釋放成功
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

// 喚醒佇列中排隊的執行緒
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 獲取head節點的next
    Node s = node.next;
    // 如果下一個為null,或者下一個節點的狀態為取消
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 從後往前,找到節點狀態正常的,並且離head最先的節點。(思考問題:為什麼從後往前找節點)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 如果離head最近的節點,不為null
    if (s != null)
        // 喚醒執行緒!
        LockSupport.unpark(s.thread);
}

(思考問題:釋放阻塞執行緒時,為什麼從後往前找狀態正常的節點)