ReadWriteLock之公平鎖解析(一)

接下來探討ReadWriteLock的公平鎖實現(xiàn), 也是分如下場景分析

情景1 三個線程都是讀

public static void main(String[] args){
    final Printer printer = new Printer();
    Thread thread1 = new Thread(){
        @Override
        public void run() {
            try {
                printer.read("test1");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    Thread thread2 = new Thread(){
        @Override
        public void run() {
            try {
                printer.read("test2");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    Thread thread3 = new Thread(){
        @Override
        public void run() {
            try {
                printer.read("test3");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    thread1.start();
    thread2.start();
    thread3.start();
}
ReadWriteLock lock = new ReentrantReadWriteLock(true);

public void read(String str) throws Exception{
    lock.readLock().lock();
    try {
        System.out.println(str);
        Thread.sleep(200);
    }finally {
        lock.readLock().unlock();
    }
}
public void lock() {
    sync.acquireShared(1);
}
// AQS
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// sync
protected final int tryAcquireShared(int unused) {
    // Thread-0
    Thread current = Thread.currentThread();
    // c = 0
    int c = getState();
    // 不走此分支
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // r = 0
    int r = sharedCount(c);
    // 
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        ......
    }
    ......
}
static final class FairSync extends Sync {
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}
// 這段代碼目的就是看queue里是否還存在想要獲取鎖的線程
// 此段代碼解析在《ReentrantLock之公平鎖unlock》這片文章中可看到
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    // 此時h和t都是null, h == t, 所以返回false
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

接下來回到tryAcquireShared方法中

protected final int tryAcquireShared(int unused) {
    ......
    // readerShouldBlock方法返回false, 
    if (!readerShouldBlock() &&
        // r = 0, 確實比MAX_COUNT小
        r < MAX_COUNT &&
        // 成功設(shè)置state為65536
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 因為r=0, 進入此分支
        if (r == 0) {
            // firstReader為thread-0
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            ......
        } else {
            ......
        }
        // 從此返回 
        return 1;
    }
    ......
}

此時線程1已經(jīng)獲取讀鎖, 接下來線程2開始執(zhí)行

public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
    // thread-1
    Thread current = Thread.currentThread();
    // state = 65536
    int c = getState();
    // 沒加寫鎖, 跳過此分支
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // r = 1, 表示此時已經(jīng)獲得一次讀鎖
    int r = sharedCount(c);
    // readerShouldBlock方法仍舊返回false
    if (!readerShouldBlock() &&
        // MAX_COUNT為65535, 也就是獲取讀鎖的最大次數(shù)就是65535
        // 此時為1, 并沒有大于MAX_COUNT
        r < MAX_COUNT &&
        // 已經(jīng)設(shè)置state為65536*2
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 不走此分支
        if (r == 0) {
             ......
        } 
        // 由于非重入鎖也不走此分支
        else if (firstReader == current) {
            firstReaderHoldCount++;
        } 
        // 最終走此分支
        else {
            // rh為null
            HoldCounter rh = cachedHoldCounter;
            // 走此分支
            if (rh == null || rh.tid != getThreadId(current))
                // 新建HoldCounter對象
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
               ......
            // 并將HoldCounter對象count(對應(yīng)重入次數(shù))+1
            rh.count++;
        }
        return 1;
    }
    ......
}

到此第二個線程也獲得讀鎖成功

接下來第三個線程開始執(zhí)行

// ReadLock
public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
    // thread-2
    Thread current = Thread.currentThread();
    // 131072 ==> 65536 * 2
    int c = getState();
    // 沒有讀鎖, 此分支不走
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // r = 2
    int r = sharedCount(c);
    // 都是讀操作, 沒有阻塞
    if (!readerShouldBlock() &&
        // 讀次數(shù)小于65535
        r < MAX_COUNT &&
        // state = 65536 * 3 ==> 196608
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            ......
        } else if (firstReader == current) {
            ......
        }
        // 走此分支 
        else {
            // cachedHoldCounter表示上一個成功獲取讀鎖的線程
            // 在此就是線程thread-1
            HoldCounter rh = cachedHoldCounter;
            // 由于rh.tid保存的是thread-1的線程id
            // 與當前線程保存的線程id不相等
            // 所以最終還是走此分支
            if (rh == null || rh.tid != getThreadId(current))
                // 創(chuàng)建一個HoldCounter對象
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            // 將其獲得鎖次數(shù)加一
            rh.count++;
        }
        // 返回
        return 1;
    }
    ...
}

這時線程3也獲取到讀鎖

情景2 前兩個線程是讀, 最后一個線程是寫

由于線程1和線程2的執(zhí)行流程與情景1相同, 所以不再做分析, 接下來就做線程3的分析

public void lock() {
    sync.acquire(1);
}
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    // thread-2
    Thread current = Thread.currentThread();
    // c = 65536 * 2
    int c = getState();
    // w = 0
    int w = exclusiveCount(c);
    // 進入此分支
    if (c != 0) {
        // 由于w為0, 進入此分支
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        ......
    }
    ......
}

由于之前已經(jīng)有讀操作獲取鎖, 所以寫操作會被阻塞住, 接下來是執(zhí)行寫操作的線程如何被阻塞

回到acquire方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // 這塊代碼就沒什么特別的
        // ReentrantLock阻塞線程也用的這個
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市孕暇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聋袋,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穴吹,死亡現(xiàn)場離奇詭異幽勒,居然都是意外死亡,警方通過查閱死者的電腦和手機港令,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門啥容,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人顷霹,你說我怎么就攤上這事咪惠。” “怎么了淋淀?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵遥昧,是天一觀的道長。 經(jīng)常有香客問我朵纷,道長炭臭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任袍辞,我火速辦了婚禮鞋仍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘革屠。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布似芝。 她就那樣靜靜地躺著那婉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪党瓮。 梳的紋絲不亂的頭發(fā)上详炬,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音寞奸,去河邊找鬼呛谜。 笑死,一個胖子當著我的面吹牛枪萄,可吹牛的內(nèi)容都是我干的隐岛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼瓷翻,長吁一口氣:“原來是場噩夢啊……” “哼聚凹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起齐帚,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤妒牙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后对妄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湘今,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年剪菱,在試婚紗的時候發(fā)現(xiàn)自己被綠了摩瞎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡琅豆,死狀恐怖愉豺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茫因,我是刑警寧澤蚪拦,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站冻押,受9級特大地震影響驰贷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洛巢,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一括袒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稿茉,春花似錦锹锰、人聲如沸芥炭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽园蝠。三九已至,卻和暖如春痢士,著一層夾襖步出監(jiān)牢的瞬間彪薛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工怠蹂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留善延,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓城侧,卻偏偏與公主長得像易遣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赞庶,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容