對于一個讀寫鎖來說,同一時刻,可以有多個線程拿到讀鎖,只有一個線程拿到寫鎖。一旦一個線程拿到寫鎖,他們?nèi)魏蜗胍@取讀鎖或者寫鎖的線程,都必須等待。
考慮下面這種情況
Thread1: A.readlock -> ... (已經(jīng)拿到讀鎖)
Thread2: A.readlock->... (Thread1拿到讀鎖之后,Thread2也去請求讀鎖)
很簡單,這種情況Thread2也可以順利拿到讀鎖,沒有任何問題。
如果這時候有個Thread3,他在Thread1拿到讀鎖之后,Thread2請求讀鎖之前,去請求寫鎖。
Thread1: A.readlock -> ... (已經(jīng)拿到讀鎖)
Thread3: A.writelock->...(請求寫鎖)
Thread2: A.readlock->...
那么這種情況下,Thread2和Thread3會繼續(xù)往下執(zhí)行么?
Thread3顯然是要等待的。Thread2呢?答案是:不一定。
這要取決于讀寫鎖的實(shí)現(xiàn)方法。
linux內(nèi)核的rwlock是讀寫鎖的最簡單的參考實(shí)現(xiàn)。它用一個整數(shù)counter代表一個rwlock。0代表沒有人占有鎖,-1代表有一個線程持有著寫鎖, 正整數(shù)n代表有n個線程持有讀鎖。要拿讀鎖時,如果counter小于0, 則繼續(xù)循環(huán)測試,直到counter非負(fù)。然后給counter加1,拿鎖成功。(當(dāng)然,得保證“在counter非負(fù)的情況下加1”這個操作的原子性,一般通過spinlock或者bit spinlock實(shí)現(xiàn))??梢姡绻呀?jīng)有一個線程拿著讀鎖還未釋放,另一個線程獲取讀鎖會立即成功。
這個實(shí)現(xiàn)很簡單,但是存在公平性的問題:寫者可能會被餓死。 如果有很多線程相續(xù)拿到讀鎖然后釋放讀鎖,保持counter的值始終大于0,那寫者就一直拿不到寫鎖。http://lwn.net/Articles/364583/
一個辦法是在rwlock元數(shù)據(jù)中增加一個標(biāo)記,代表是否有寫者在等待讀者。讀者要拿讀鎖時,先要等待這個標(biāo)記的清除。筆者曾經(jīng)在嵌入式環(huán)境中,使用和修改過這樣的讀寫鎖。更加先進(jìn)的方法,是讓等待者排一個FIFO隊(duì)列,比較著名的是MCS lock和CLH lock。
Java的ReentrantReadWriteLock,就是基于CLH算法。
正是由于這個排隊(duì)算法,由于Thread2在Thread3之前,因此Thread2必須等Thread3拿到鎖,做完事情,并且釋放,才能獲得讀鎖。
下面是一個簡單的實(shí)驗(yàn)的代碼
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Main {
public static void main(String[] args) {
final ReentrantReadWriteLock.ReadLock readLock;
final ReentrantReadWriteLock.WriteLock writeLock;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);
readLock = lock.readLock();
writeLock = lock.writeLock();
System.out.println("main: before readLock.lock()");
readLock.lock();
System.out.println("main: after readLock.lock()");
Thread tw = new Thread() {
@Override
public void run() {
System.out.println("tw: before writeLock.lock()");
writeLock.lock();
System.out.println("tw: after writeLock.lock()");
}
};
Thread tr = new Thread() {
@Override
public void run() {
System.out.println("tr: before readLock.lock()");
readLock.lock();
System.out.println("tr: after readLock.lock()");
}
};
try {
tw.start();
Thread.sleep(1000);
tr.start();
tw.join();
tr.join();
} catch (InterruptedException ie) {
}
}
}