一、Read-Write Lock模式
學生們正在看老師黑板上的板書,這時老師想擦掉板書,寫新的內容,這時學生們說:老師,我們還沒有看完,請先不要擦掉,於是老師就會等待大家都看完。
當線程讀取實例的狀態時,實例的狀態不會發生變化,實例的狀態僅在線程執行寫入的操作時纔會發生變化。從實例的狀態變化這個觀點來看,讀取和寫入有着本質的區別。
在Read-Write Lock模式中,讀取和寫入操作時分開的,在執行讀取操作之前,線程必須獲取於讀寫的鎖,而在執行寫入操作之前,線程必須獲取用於寫入的鎖。
由於線程執行讀取操作時,實例的狀態不會發生變化,所以多個線程可以同時讀取。但讀取時,不可以寫入。當線程執行寫入操作時,實例的狀態就會發生變化,因此,當有一個線程正在寫入時,其他線程不可以讀取或寫入。
一般來說,執行互斥處理會降低程序性能,但如果把針對寫入的互斥處理和針對讀取的互斥處理分開來考慮,則可以提高程序性能
二、示例程序
這裏編寫是多個線程對Data類的實例執行讀寫操作的程序。
類名 | 說明 |
---|---|
Main.java | 測試程序的類 |
Data.java | 操作讀寫的類 |
WriterThread.java | 表示寫入線程程序的類 |
ReaderThread.java | 表示讀取線程程序的類 |
ReadWriteLock.java | 表示操控讀寫鎖的類 |
實例程序的類圖:
1.Main類
這裏啓動6個讀取線程和2個寫入線程。
package com.viagra.Read_Write_Lock_Pattern.Lesson1;
/**
* @Auther: viagra
* @Date: 2019/11/19 17:32
* @Description:
*/
public class Main {
public static void main(String[] args) {
/**
* 這裏啓動6個讀取線程和2個寫入線程
*/
Data data = new Data(10);
new ReaderThread(data).start();
new ReaderThread(data).start();
new ReaderThread(data).start();
new ReaderThread(data).start();
new ReaderThread(data).start();
new ReaderThread(data).start();
new WriterThread(data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").start();
new WriterThread(data, "abcdefghijklmnopqrstuvwxyz").start();
}
}
2.Data類
是可以執行讀取read和寫入write操作的類,buffer對象實際讀取的對象是char的數組,lock字段保存的是該模式的主角ReadWriteLock的實例。
package com.viagra.Read_Write_Lock_Pattern.Lesson1;
/**
* @Auther: viagra
* @Date: 2019/11/19 17:32
* @Description:
*/
public class Data {
/**
* 是可以執行讀取read和寫入write操作的類
* buffer對象實際讀取的對象是char的數組
* lock字段保存的是該模式的主角ReadWriteLock的實例
*/
private final char[] buffer;
private ReadWriteLock lock = new ReadWriteLock();
/**
* 構造函數會根據參數傳入的長度來分配一個char數組,並初始化buffer字段,同時以*填滿buffer,爲初始值.
*
* @param size
*/
public Data(int size) {
this.buffer = new char[size];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = '*';
}
}
/**
* read方法執行讀取操作,實際的讀取是通過doRead方法方法執行的,而doRead方法夾在lock.readLock和lock.readUnLock之間
* read.readLock表示獲取用戶讀取的鎖
* 而read.readUnLock表示釋放用戶讀取的鎖,finally操作表示不管遇到啥情況read.readLock都會被調用.
*
* @return
* @throws InterruptedException
*/
public char[] read() throws InterruptedException {
lock.readLock();
try {
return doRead();
} finally {
lock.readUnLock();
}
}
public void write(char c) throws InterruptedException {
lock.writeLock();
try {
doWrite(c);
} finally {
lock.writeUnLock();
}
}
/**
* doRead方法用戶執行實際的讀取操作,該方法會創建一個新的char數組newbuf來複制buffer字段的內容,並返回newbuf
*
* @return
*/
private char[] doRead() {
char[] newBuf = new char[buffer.length];
for (int i = 0; i < buffer.length; i++) {
newBuf[i] = buffer[i];
}
slowly();
return newBuf;
}
/**
* doWrite方法用於執行實際的寫入操作,該方法會以參數傳入字符c來填滿buffer字段
*
* @param c
*/
private void doWrite(char c) {
for (int i = 0; i < buffer.length; i++) {
buffer[i] = c;
slowly();
}
}
/**
* 用戶輔助模擬消耗的操作
*/
private void slowly() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.WriterThread類
表示是對Data實例寫入的操作的線程,filler是一個字符串,程序會逐個讀取filler中的字符,並write到Data的實例中,每寫一次,線程就會在0~3000毫秒內隨即sleep一段時間,此外nextChar方法用戶獲取下一次應該寫如的字符。
package com.viagra.Read_Write_Lock_Pattern.Lesson1;
import java.util.Random;
/**
* @Auther: viagra
* @Date: 2019/11/19 17:32
* @Description:
*/
public class WriterThread extends Thread {
/**
* 表示是對Data實例寫入的操作的線程,filler是一個字符串,程序會逐個讀取filler中的字符,並write到Data的實例中.
* 每寫一次,線程就會在0~3000毫秒內隨即sleep一段時間,此外nextChar方法用戶獲取下一次應該寫如的字符.
*/
private final Data data;
private static final Random random = new Random();
private final String filler;
private int index = 0;
public WriterThread(Data data, String filler) {
this.data = data;
this.filler = filler;
}
public void run() {
try {
while (true) {
char c = nextChar();
//System.out.println(Thread.currentThread().getName() + " wwwwwwwwwrite : " + c);
data.write(c);
Thread.sleep(random.nextInt(3000));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private char nextChar() {
char c = filler.charAt(index);
index++;
if (index >= filler.length()) {
index = 0;
}
return c;
}
}
4.ReaderThread類
表示的執行讀取操作的線程,該類會循環調用data.read方法,並顯示讀取到的char數組。
package com.viagra.Read_Write_Lock_Pattern.Lesson1;
/**
* @Auther: viagra
* @Date: 2019/11/19 17:32
* @Description:
*/
public class ReaderThread extends Thread {
/**
* 表示的執行讀取操作的線程,該類會循環調用data.read方法,並顯示讀取到的char數組.
*/
private final Data data;
public ReaderThread(Data data) {
this.data = data;
}
public void run() {
try {
while (true) {
char[] buffer = data.read();
System.out.println(Thread.currentThread().getName() + " reads " + String.valueOf(buffer));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5.ReadWriteLock類
package com.viagra.Read_Write_Lock_Pattern.Lesson1;
/**
* @Auther: viagra
* @Date: 2019/11/19 17:32
* @Description:
*/
public class ReadWriteLock {
/**
* 有衝突
* 當線程想要獲取讀取鎖時:
* 1.如果有線程正在執行寫入,則等待,
* 2.如果有線程正在執行讀取,則無需等待
* 當線程想要獲取寫入鎖時:
* 3.如果有線程正在執行寫入,則等待
* 4.如果有線程正在執行讀取,則等待
*/
private int readingReaders = 0; //實際正在讀取的線程個數
private int waitingWriters = 0; //正在等待寫入線程的個數
private int writingWriters = 0; //實際正在寫入線程的個數
private boolean preferWriter = true; //若寫入優先,則爲true
/**
* 讀取
* 如果寫入線程大於0或者寫入有true且等待的線程大於0,則等待
*
* @throws InterruptedException
*/
public synchronized void readLock() throws InterruptedException {
while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) {
wait();
}
readingReaders++; //實際正在讀取的線程個數加1
}
/**
* 釋放讀取,讀取-1,notifyAll
*/
public synchronized void readUnLock() {
readingReaders--; //實際正在讀取的線程個數減1
preferWriter = true;
notifyAll();
}
/**
* 寫入
* 如果讀取線程大於0或者寫入線程大於0,則等待
*
* @throws InterruptedException
*/
public synchronized void writeLock() throws InterruptedException {
waitingWriters++; //正在等待寫入的線程數加1
try {
while (readingReaders > 0 || writingWriters > 0) {
wait();
}
} finally {
waitingWriters--; //正在等待寫入的線程數減1
}
writingWriters++; //實際正在寫入的線程加1
}
/**
* 寫入釋放 -1 ,notifyAll
*/
public synchronized void writeUnLock() {
writingWriters--; //正在等待寫入的線程數減1
preferWriter = false;
notifyAll();
}
}
6.運行結果
Thread-0 reads **********
Thread-5 reads **********
Thread-1 reads **********
Thread-2 reads **********
Thread-4 reads **********
Thread-3 reads **********
Thread-4 reads AAAAAAAAAA
Thread-3 reads AAAAAAAAAA
Thread-2 reads AAAAAAAAAA
Thread-1 reads AAAAAAAAAA
Thread-0 reads AAAAAAAAAA
Thread-5 reads AAAAAAAAAA
Thread-5 reads aaaaaaaaaa
Thread-4 reads aaaaaaaaaa
Thread-1 reads aaaaaaaaaa
readLock方法和writeLock方法都使用了Guarded Suspension模式,就是有守護模式。
readLock方法:
在線程開始讀取操作之前,readLock方法被調用,在線程執行讀取操作時,有其他線程正在做讀取操作沒有關係,但是不允許存在正在寫入操作的線程,其守護條件就是沒有線程正在做寫入操作。
writeLock方法:
在線程開始做寫入操作之前,writeLock方法就會被調用,其守護條件爲沒有線程正在執行讀取操作或寫入操作。
三、Read-Write Lock模式中的角色
1.Reader(讀者)
執行read操作。
2.Writer(寫者)
執行write操作。
3.SharedResouce(共享資源)
表示的是Reader和Writer角色二者共享的資源,在實例程序中,Data類爲SharedResouce。
4.ReadWriteLock(讀寫鎖)
ReadWriteLock角色提供了SharedResouce實現read操作和write操作時所需的鎖,在實例中,是爲ReadWriteLock類。
ReadWriteLock模式的類圖: