前言
在沒有讀寫鎖之前,ReentrantLock 雖然可以保證了線程安全,但是也浪費了一定的資源,因為如果多個讀操作同時進行,其實并沒有線程安全問題,我們可以允許讓多個讀操作并行,以便提高程式效率。
但是寫操作不是線程安全的,如果多個線程同時寫,或者在寫的同時進行讀操作,便會造成線程安全問題。
我們的讀寫鎖就解決了這樣的問題,它設定了一套規則,既可以保證多個線程同時讀的效率,同時又可以保證有寫入操作時的線程安全。
整體思路是它有兩把鎖,第 1 把鎖是寫鎖,獲得寫鎖之後,既可以讀資料又可以修改資料,而第 2 把鎖是讀鎖,獲得讀鎖之後,隻能檢視資料,不能修改資料。讀鎖可以被多個線程同時持有,是以多個線程可以同時檢視資料。
在讀的地方合理使用讀鎖,在寫的地方合理使用寫鎖,靈活控制,可以提高程式的執行效率。
1.讀寫鎖的擷取規則
我們在使用讀寫鎖時遵守下面的擷取規則:
如果有一個線程已經占用了讀鎖,則此時其他線程如果要申請讀鎖,可以申請成功。
如果有一個線程已經占用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖,因為讀寫不能同時操作。
如果有一個線程已經占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,都必須等待之前的線程釋放寫鎖,同樣也因為讀寫不能同時,并且兩個線程不應該同時寫。
一句話總結:要麼是一個或多個線程同時有讀鎖,要麼是一個線程有寫鎖,但是兩者不會同時出現。也可以總結為:讀讀共享、其他都互斥(寫寫互斥、讀寫互斥、寫讀互斥)。
2.使用案例
下面我們舉個例子來應用讀寫鎖,ReentrantReadWriteLock 是 ReadWriteLock 的實作類,最主要的有兩個方法:readLock() 和 writeLock() 用來擷取讀鎖和寫鎖。
代碼如下:
public class ReadWriteLockDemo {
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
public static void main(String[] args) {
new Thread(()->read()).start();
new Thread(()->read()).start();
new Thread(()->write()).start();
new Thread(()->write()).start();
}
private static void read() {
String threadName = Thread.currentThread().getName();
readLock.lock();
try {
System.out.println(threadName + "-擷取讀鎖(readLock),讀取資料...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadName + "-釋放讀鎖(readLock)");
readLock.unlock();
}
}
private static void write() {
String threadName = Thread.currentThread().getName();
writeLock.lock();
try {
System.out.println(threadName + "-擷取寫鎖(writeLock),寫入資料...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadName + "-釋放讀鎖(writeLock)");
writeLock.unlock();
}
}
}
執行結果:
Thread-0-擷取讀鎖(readLock),讀取資料...
Thread-1-擷取讀鎖(readLock),讀取資料...
Thread-0-釋放讀鎖(readLock)
Thread-1-釋放讀鎖(readLock)
Thread-2-擷取寫鎖(writeLock),寫入資料...
Thread-2-釋放寫鎖(writeLock)
Thread-3-擷取寫鎖(writeLock),寫入資料...
Thread-3-釋放寫鎖(writeLock)
可以看到
線程0和線程1都獲得了讀鎖,讀取資料;
但是線程2獲得寫鎖之後,線程3線上程2釋放了寫鎖之後,才擷取寫鎖,寫入資料。
3.讀寫鎖适用場合
最後我們來看下讀寫鎖的适用場合,相比于 ReentrantLock 适用于一般場合,ReadWriteLock 适用于讀多寫少的情況,合理使用可以進一步提高并發效率。