Read-Write-Lock 模式


如果有一個資料檔有可能同時間會有許多客戶端對它進行讀取與寫入的動作,則必須注意資料的同步問題,像是兩個寫入者進行寫入時,後一個寫入者的資料會有可 能將次一個寫入者的資料覆蓋掉;而有時您希望讀取者看到的是最新的資料,如果在讀取的時候,有寫入者想要對資料進行寫入,則最好等待讀取者讀取完畢,相反 的如果在寫入時有客戶想要讀取資料,則最好等待,以確保讀出來的資料是最新的資料。

讀取寫入的同步問題向來是難解的問題之一,有幾個可行的作法,例如若有寫入的動作時,則讀取者以唯讀模式開啟;或是如果有開啟資料檔的動作時,無論是讀取 或是寫入,後一個開啟檔案的客戶都一律以唯讀模式開啟;還有最乾脆的作法,就是將這個問題由客戶決定,在開啟檔案時若已有其他人開啟中,則提供選項讓客戶 決定要不要以唯讀模式開啟,通常這個作法是提供給檔案的擁有者使用。

Read-Write-Lock 模式提供給被讀取或寫入的資料一個鎖定物件,在讀取或寫入時要向鎖定物件形式上讀取鎖定,實際上真正是否鎖定共用資源,由鎖定物件來判斷。

一個簡單的Java程式例子如下所示:
 public void readData() {
    lock.readLock();
    doRead();
    lock.readUnLock();
 }

 public void writeData() {
    lock.writeLock();
    doWrite();
    lock.writeUnLock();
 }


下圖讀取者讀取資料時的Sequence Diagram示例:  
Read-Write-Lock


如果可以同時讀取,現在假設有個讀取者已經取得鎖,另一個讀取者其實也還是可以如下形式上取得鎖定並讀取。

如果現在只剩一個讀取者,而寫入者試圖進行寫入,它也試圖先取得鎖定,但發現鎖已經被讀取的一方擁有,於是先進入等待,直到讀取的一方解除鎖定為止:

Read-Write-Lock



而最主要的關鍵還是在於鎖的實現,在Java中可以用wait()、notify()來實現,實現的片段如下:
 private boolean writerFirst = true; // 寫入優先
 
 public synchronized void readLock() {
    try {
        while(writingWriters > 0 || (writerFirst && waitingWriters > 0)) {
            wait();
        }
    }
    catch(InterruptedException e) {
        e.printStackTrace();
    }

    readingReaders++;
 }
 
 public synchronized void readUnLock() {
    readingReaders--;
    writerFirst = true;
    notifyAll();
 }
 
 public synchronized void writeLock() {
    waitingWriters++
    try {
        while(readingReaders > 0 || writingWriters > 0) {
            wait();
        }
    }
    catch(InterruptedException e) {
        e.printStackTrace();
    }
    finally {
        waitingWriters--;
    }

    writingWriters++;
 }
 
 public synchronized void writeUnLock() {
    writingWriters--;
    writerFirst = false;
    notifyAll();
 }
 
其中writerFirst是寫入優先的旗標,它確保只要有寫入的執行緒在等待時,在解除鎖定的時候,可以優先由寫入執行緒取得鎖定,以確保讀取者讀取到 的資料可以是最新的,但缺點就是寫入的動作很頻繁時,讀取者必須等待的機會將增多,相反的若設定為讀取優先,則讀取時的回應性會增高,但資料更新的速率將 會下降,實際使用時要偏好哪一方,必須視應用的場合而定。