本文作者:王一飛,叩丁狼高級講師。原創文章,轉載請註明出處。
接上篇,本篇講解線程另外一個設計模式:Read-Write Lock Pattern.
概念
Read是讀, 指獲取/查詢數據的線程, Write是寫,指操作(增刪改)數據的線程.
Read-Write Lock 模式要求:
1>在讀模式時,多個線程可以同時執行讀操作,但不允許寫操作
2>在寫模式時,只允許一個線程執行寫操作,不允許其他線程進行讀寫
簡單講就是常說的:讀寫互斥
參與角色
Reader
讀線程,擁有對共享資源讀操作權限
Writer
寫線程,擁有對共享資源寫操作權限
Resource
共享資源, Reader/writer角色共享的資源, 一般具有2個方法,一個不改變資源狀態的讀操作, 一個可改變資源狀態的寫操作。
讀寫鎖
鎖對象,提供讀鎖, 寫鎖,在reader/writer角色讀寫時,加鎖實現讀寫互斥
演示案例
需求:5個線程讀,5個線程寫實現讀寫互斥
Read-Write Lock Pattern難點在於怎麼控制讀寫互斥,而讀寫互斥可以拆分爲:
1>讀取與寫入的衝突
2>寫入與寫入的衝突
3>寫入與讀取的衝突
思考:讀寫怎麼互斥法
1:讀模式時(線程嘗試讀操作)
a>如果此時已經有線程在讀,允許讀
b>如果此時已經有線程在寫,暫停當前線程
2:寫模式時(線程嘗試寫操作)
a>如果此時已經有線程在讀,暫定當前線程
b>如果此時已經有線程在寫,暫停當前線程
根據上面分析,很容易可以找到需求突破口
1>線程滿足某個條件需要暫停等待----線程wait/notifyall操作
2>這裏某個條件是當前在讀/在寫的線程數量-----線程計數
3>使用同一鎖對象對多線程的讀寫互斥控制
結論:
設計:ReadWriteLock 讀寫鎖控制類
readCount : 當前讀線程數量
writeCount: 當前寫線程數量
readLock(): 獲取讀鎖,成功執行,不成功等待
unReadLock(): 操作結束後釋放讀鎖
writeLock: 獲取寫鎖,成功執行,不成功等待
unWriteLock: 操作結束後釋放寫鎖
//互斥鎖控制類
public class ReadWriteLock {
//讀線程個數
private int readCount;
//寫線程個數
private int writeCount;
public synchronized String info(){
return "讀線程:" + readCount + ", 寫線程:" + writeCount;
}
//獲取讀鎖
public synchronized void readLock() throws InterruptedException {
//此時有寫線程操作,暫停
while(writeCount > 0 ){
wait();
}
//沒有讀寫線程+1
readCount++;
System.out.println("讀模式:" +info());
}
//釋放讀鎖
public synchronized void unReadLock(){
readCount--; //讀線程減少
notifyAll(); //喚醒等待線程:讀或者寫
}
//獲取寫鎖
public synchronized void writeLock() throws InterruptedException {
//此時有讀線程操作,暫停
//此時有寫線程操作,暫停
while(readCount > 0 || writeCount > 0){
wait();
}
//沒有讀寫線程+1
writeCount++;
System.out.println("寫模式:" +info());
}
//釋放寫鎖
public synchronized void unWriteLock(){
writeCount--; //讀線程減少
notifyAll(); //喚醒等待線程:讀或者寫
}
}
操作的資源
//共享資源
//共享資源
public class Resource {
//讀與寫數據
private String data = "init";
//控制data數據的讀寫互斥
private ReadWriteLock lock = new ReadWriteLock();
//對data數據的讀操作
public void read() throws InterruptedException {
lock.readLock();
try {
System.out.println(Thread.currentThread().getName()+"--read--:" + data);
}finally {
lock.unReadLock();
}
}
//對data數據的寫操作
public void write(String d) throws InterruptedException {
lock.writeLock();
try {
data = d;
System.out.println(Thread.currentThread().getName()+" -write-:" + data);
}finally {
lock.unWriteLock();
}
}
}
測試類
public class App {
public static void main(String[] args) {
final Resource data = new Resource();
//5個讀
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
public void run() {
while (true){
try {
data.read();
Thread.sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"read_" + i).start();
}
//5個寫
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
public void run() {
while (true){
try {
data.write(Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"write_" + i).start();
}
}
}
執行效果:
讀模式:讀線程:1, 寫線程:0
read_0--read--:init
讀模式:讀線程:2, 寫線程:0
read_3--read--:init
讀模式:讀線程:3, 寫線程:0
read_2--read--:init
讀模式:讀線程:4, 寫線程:0
read_1--read--:init
讀模式:讀線程:1, 寫線程:0
read_4--read--:init
寫模式:讀線程:0, 寫線程:1
write_0 -write-:write_0
寫模式:讀線程:0, 寫線程:1
write_1 -write-:write_1
寫模式:讀線程:0, 寫線程:1
write_2 -write-:write_2
寫模式:讀線程:0, 寫線程:1
write_3 -write-:write_3
寫模式:讀線程:0, 寫線程:1
write_4 -write-:write_4
寫模式:讀線程:0, 寫線程:1
write_0 -write-:write_0
讀模式:讀線程:1, 寫線程:0
read_3--read--:write_0
寫模式:讀線程:0, 寫線程:1
write_4 -write-:write_4
讀模式:讀線程:1, 寫線程:0
read_1--read--:write_4
讀模式:讀線程:1, 寫線程:0
read_4--read--:write_4
讀模式:讀線程:1, 寫線程:0
read_2--read--:write_4
寫模式:讀線程:0, 寫線程:1
write_0 -write-:write_0
寫模式:讀線程:0, 寫線程:1
write_1 -write-:write_1
讀模式:讀線程:1, 寫線程:0
從輸出效果看, 當讀線程有值時,不會進行寫操作, 但讀操作可以同時進行,比如:讀模式時,讀線程:4,寫線程爲:0。 反之,當寫線程出現1時,寫線程全部爲0, 另外寫線程個數不會超過1
使用場景
Read-Write Lock Pattern講究是讀寫互斥,讀不進行安全控制, 寫時需要控制。相對讀寫都進行安全控制的模式來說,性能上還是有一定提升, 但不絕對。Read-Write Lock Pattern更使用與讀頻率遠高與寫頻率的安全操作。
jdk中其實也有讀寫鎖操作
//共享資源
public class Resource {
//讀與寫數據
private String data = "init";
//控制data數據的讀寫互斥
// private ReadWriteLock lock = new ReadWriteLock();
//jdk
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
//對data數據的讀操作
public void read() throws InterruptedException {
//lock.readLock();
readLock.lock();
try {
System.out.println(Thread.currentThread().getName()+"--read--:" + data);
}finally {
//lock.unReadLock();
readLock.unlock();
}
}
//對data數據的寫操作
public void write(String d) throws InterruptedException {
//lock.writeLock();
writeLock.lock();
try {
data = d;
System.out.println(Thread.currentThread().getName()+" -write-:" + data);
}finally {
// lock.unWriteLock();
writeLock.unlock();
}
}
}