同步鎖——ReentrantLock



本博客系列是學習並發編程過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。

並發編程系列博客傳送門


Lock接口簡介

在JUC包下面有一個java.util.concurrent.locks包,這個包提供了一系列基礎的鎖工具,對傳統的synchronizd、wait和notify等同步機制進行補充和增強。下面先來介紹下這個Lock接口。

Lock接口可以視為synchronized的增強版,提供了更靈活的功能。相對於synchronized,Lock接口還提供了限時鎖等待、鎖中斷和鎖嘗試等功能。

該接口的定義如下


public interface Lock {
// 嘗試去獲得鎖
// 如果鎖不可用,當前線程會變得不可用,直到獲得鎖為止。(中途會忽略中斷)    
void lock();

// 嘗試去獲取鎖,如果鎖獲取不到,線程將不可用
// 直到獲取鎖,或者被其他線程中斷
// 線程在獲取鎖操作中,被其他線程中斷,則會拋出InterruptedException異常,並且將中斷標識清除。
void lockInterruptibly() throws InterruptedException;

// 鎖空閑時返回true,鎖不空閑是返回false
// 該方法不會引起當前線程阻塞
boolean tryLock();

// 在unit時間內成功獲取鎖,返回true
// 在unit時間內未成功獲取鎖,返回false
// 如果當前線程在獲取鎖操作中,被其他線程中斷,則會拋出InterruptedException異常,並且將中斷標識清除。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

// 釋放鎖
void unlock();

// 獲取一個綁定到當前Lock對象的Condition對象
// 獲取Condition對象的前提是當前線程持有Lock對象
Condition newCondition();
}

關於上面的lock()和lockInterruptibly()方法,有如下區別:

lock()方法類似於使用synchronized關鍵字加鎖,如果鎖不可用,出於線程調度目的,將禁用當前線程,並且在獲得鎖之前,該線程將一直處於休眠狀態。
lockInterruptibly()方法顧名思義,就是如果鎖不可用,那么當前正在等待的線程是可以被中斷的,這比synchronized關鍵字更加靈活。

Lock接口的經典用法

Lock lock = new ReentrantLock();
//嘗試獲取鎖,如果當前該鎖沒有被其他線程持有,則當前線程獲取該鎖並返回true,否則返回false。
//該方法不會引起當前線程阻塞
if (lock.tryLock()) {
    try {
        // manipulate protected state
    } finally {
        lock.unlock();
    }
} else {
    // perform alternative actions
}

或者是

lock.lock()
try {
        // manipulate protected state
    } finally {
        lock.unlock();
    }

這邊不要將獲取鎖的過程寫在try塊中,因為如果在獲取鎖(自定義鎖的實現)時發生了異常,異常拋出的同時,也會導致鎖無故釋放

ReentrantLock

ReentrantLock類是一個可重入的獨占鎖,除了具有和synchronized一樣的功能外,還具有限時鎖等待、鎖中斷和鎖嘗試等功能。

ReentrantLock底層是通過繼承AQS來實現獨占鎖功能的。

公平鎖和非公平鎖

關於ReentrantLock,有兩個很重要的概念需要學習:公平鎖和非公平鎖

查看ReentrantLock的源代碼,我們會看到兩個構造函數,分為對應構造公平鎖和非公平鎖。

//默認構造非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
//true構造公平鎖,false構造非公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平鎖:是指線程在搶占鎖失敗后會進入一個等待隊列,先進入隊列的線程會先獲得鎖。公平性體現在先來先得。
非公平鎖:是指線程搶占鎖失敗后會進入一個等待隊列,但是這些等待線程誰能先獲得鎖不是按照先來先得的規則,而是隨機的。不公平性體現在后來的線程可能先得到鎖。

如果有很多線程競爭一把公平鎖,系統的總體吞吐量(即速度很慢,常常極其慢)比較低,因為此時在線程調度上面的開銷比較大。

原因是采用公平策略時,當一個線程釋放鎖時,需要先將等待隊列中的線程喚醒。這個喚醒的調度過程是比較耗費時間的。如果使用非公平鎖的話,當一個線程釋放鎖之后,可用的線程能立馬獲得鎖,效率較高。

ReentrantLock代碼實現

1. 非公平鎖代碼

static final class NonfairSync extends Sync {

    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        //如果沒有線程占據鎖,則占據鎖,也就是將state從0設置為1
        //這種搶占方式不要排隊,有人釋放了鎖,你可以直接插到第一位
        //去搶,只要你能搶到
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
        //否則嘗試搶占鎖
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

通過之前對AQS的介紹,我們知道搶占鎖的時候會調用 tryAcquire 方法。非公平鎖的這個方法直接調用了父類中的nonfairTryAcquire


final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //鎖已經被釋放,則直接占據鎖
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //否則判斷鎖是不是之前被自己占用過,並設置重入次數
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

1. 公平鎖代碼


static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //沒人在隊列中排隊,並且鎖已經被釋放才能搶占到鎖,否則去隊列中排隊
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //設置重入次數
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM