2010年9月26日 星期日

[ Java設計模式 ] 多線程設計模式 : Read-Write Lock Pattern (大家想看就看吧, 不過看的時候不能寫喔)


前言 :
老師寫在黑板的說明, 全班學生一起看. 當大家還在看時, 老師想擦掉黑板寫上新的內容, 這時學生出聲了 : “老師請先不要擦”, 於是老師等大家都看完. 當線程 "讀取" 物件狀態時, 物件狀態不會改變. 會使物件狀態改變的只有”寫入”的操作. 把焦點放在物件的狀態, 則讀與寫的動作將視為不同的東西. 這裡介紹的Read-Write Lock Pattern 將讀取與寫入分開來處理, 在讀取時必須獲得讀取的鎖定. 而要寫入時, 則必須獲取寫入的鎖定. 因為在讀取時, 物件狀態不會改變, 所以就算有多個線程在進行讀取都沒有關係. 但在讀取時, 則不可以進行寫入的操作. 寫入時物件狀態會改變, 於是當物件被寫入時, 就不可以被其他線程讀取或寫入. 另外Read-Write Lock Pattern 也有人稱 Readers and Writers Pattern


Pattern 參予者 :
* Reader 參予者 :
Reader 參予者會對 SharedResource 參予者進行 read 操作, 在範例程序中代表 ReaderThread.

* Writer 參予者 :
Writer 參予者會對 SharedResource 參予者進行 write 操作. 在範例程序中代表 WriterThread.

*SharedResource 參予者 :
SharedResource 參予者代表 Reader 與 Writer 參予者所共享的資源, SharedResource 參予者會提供不會變更內部狀態的操作 (read) 與會改變內部狀態操作 (write), 在範例中SharedResource 扮演 Data.

ReadWriteLock 參予者 :
ReadWriteLock 參予者提供對 SharedResource 參予者進行 read操作與 write 操作時所需要的鎖定. 範例程序中 ReadWriteLock 扮演 ReadWriteLock 類別. 下圖為對應的UML 類別圖 :


使用時機 :
* 當有Shared Resource 需要被多線程讀取或寫入時, 其中讀取不會改變 Shared Resource 的狀態, 而寫入操作則會改變 Shared Resource內部狀態時.


使用範例 :
* ReadWriteLock 類別
這個類別用來提供SharedResource 讀取與寫入的鎖定, 其中當Shared Resource 有線程正在讀取時, 其他線程可以讀取, 但不能寫入; 當Shared Resource 有線程在寫入時, 其他線程不能讀取或寫入. 代碼如下 :
  1. package dp.thread.ch6;  
  2.   
  3. public class ReadWriteLock {  
  4.     private int readingReaders = 0;  //(A)實際正在讀取的線程數目  
  5.     private int waitingWriters = 0;  //(B)實際正在等待寫入的線程數目  
  6.     private int writingWriters = 0;  //(C)實際正在寫入的線程數目  
  7.     private boolean preferWriter = true;  
  8.   
  9.     public synchronized void readLock() throws InterruptedException {  
  10.         while(writingWriters > 0 || (preferWriter && waitingWriters > 0)){  
  11.             wait();  
  12.         }  
  13.         readingReaders++; // (A)正在讀取線程數目加一  
  14.     }  
  15.   
  16.     public synchronized void readUnlock() {  
  17.         readingReaders--; // (A)正在讀取線程數目減一  
  18.         preferWriter = true;  
  19.         notifyAll();  
  20.     }  
  21.   
  22.     public synchronized void writeLock() throws InterruptedException {  
  23.         waitingWriters++; // (B)正在等待寫入線程數目加一  
  24.         try{  
  25.             while (readingReaders > 0 || writingWriters >0) {  
  26.                 wait();  
  27.             }  
  28.         }finally{  
  29.             waitingWriters--; // (B)正在等待寫入線程數目減一  
  30.         }  
  31.         writingWriters++; // (C)實際寫入線程數目加一  
  32.     }  
  33.   
  34.     public synchronized void writeUnlock() {  
  35.         writingWriters--;  // (C)實際寫入線程數目減一  
  36.         preferWriter = false;  
  37.         notifyAll();  
  38.     }      
  39. }  
* WriteThread 類別 :
WriteThread 類別表示用來對 Shared Resource (Data 類別) 寫入的線程, 代碼如下 :
  1. package dp.thread.ch6;  
  2.   
  3. import java.util.Random;  
  4.   
  5. public class WriteThread extends Thread{  
  6.     private static final Random random = new Random();  
  7.     private final Data data;  
  8.     private final String filter;  
  9.     private int index = 0;  
  10.   
  11.     public WriteThread(Data data, String filter) {  
  12.         this.data = data;  
  13.         this.filter = filter;  
  14.     }  
  15.   
  16.     public void run(){  
  17.         try{  
  18.             while(true) {  
  19.                 char c = nextChar();                  
  20.                 data.write(c);  
  21.                 System.out.println(Thread.currentThread().getName()+" writes "+c+" success!");  
  22.                 Thread.sleep(random.nextInt(1000));  
  23.                 if(index <0 ){  
  24.                     break;  
  25.                 }  
  26.             }  
  27.         }catch(InterruptedException e){  
  28.             e.printStackTrace();  
  29.         }  
  30.     }  
  31.   
  32.     private char nextChar(){  
  33.         char c = filter.charAt(index);  
  34.         index++;  
  35.         if(index >= filter.length()) {  
  36.             index = -1;  
  37.         }  
  38.         return c;  
  39.     }  
  40. }  
* ReaderThread 類別 :
ReadThread 類別用來模擬對 Shared Resource(Data 類別) 進行讀取的操作. 代碼如下 :
  1. package dp.thread.ch6;  
  2.   
  3. import java.util.Random;  
  4.   
  5. public class ReaderThread extends Thread{  
  6.     private final Data data;  
  7.     private int time = 10;  
  8.     private static final Random random = new Random();  
  9.   
  10.     public ReaderThread(Data data, int t) {  
  11.         this.data = data;  
  12.         if(t>0) {  
  13.             time = t;  
  14.         }  
  15.     }  
  16.   
  17.     public void run(){  
  18.         int i=0;  
  19.         try{  
  20.             while(i
  21.                 Thread.sleep(random.nextInt(1500));  
  22.                 char[] readBuf = data.read();  
  23.                 System.out.println(Thread.currentThread().getName()+" reads "+String.valueOf(readBuf)+" for "+(i+1)+"times/"+time);  
  24.                 i++;                  
  25.             }  
  26.         }catch(InterruptedException ie){  
  27.             ie.printStackTrace();  
  28.         }  
  29.     }  
  30. }  
* Data 類別 :
Data 類別用來模擬 Shared Resource, 可以被線程讀取與寫入, 代碼如下 :
  1. package dp.thread.ch6;  
  2.   
  3. public class Data {  
  4.     private final char[] buffer;  
  5.     private final ReadWriteLock lock = new ReadWriteLock();  
  6.   
  7.     public Data(int size){  
  8.         this.buffer = new char[size];  
  9.         for(int i=0;i
  10.             buffer[i] = '*';  
  11.         }  
  12.     }  
  13.   
  14.     public char[] read() throws InterruptedException {  
  15.         lock.readLock();  
  16.         try{  
  17.             return doRead();  
  18.         }finally{  
  19.             lock.readUnlock();  
  20.         }  
  21.     }  
  22.   
  23.     public void write(char c) throws InterruptedException{  
  24.         lock.writeLock();  
  25.         try{  
  26.             doWrite(c);  
  27.         }finally{  
  28.             lock.writeUnlock();  
  29.         }  
  30.     }  
  31.   
  32.     private char[] doRead(){  
  33.         char[] newbuf = new char[buffer.length];  
  34.         for(int i=0;i
  35.             newbuf[i] = buffer[i];  
  36.         }  
  37.         slowly();  
  38.         return newbuf;  
  39.     }  
  40.   
  41.     private void doWrite(char c) {  
  42.         for(int i=0;i
  43.             buffer[i] = c;  
  44.             slowly();  
  45.         }  
  46.     }  
  47.   
  48.     private void slowly(){  
  49.         try{  
  50.             Thread.sleep(50);  
  51.         }catch(InterruptedException ie){  
  52.             ie.printStackTrace();  
  53.         }  
  54.     }  
  55. }  
* Main 類別 :
Main 類別用來創建WriterThread類別, ReaderThread類別 與 Data 類別, 並進行Demo. 代碼如下 :
  1. package dp.thread.ch6;  
  2.   
  3. public class Main {  
  4.     public static void main(String args[]) {  
  5.        Data data = new Data(10);  
  6.        new ReaderThread(data,20).start();  
  7.        new ReaderThread(data,25).start();  
  8.        new ReaderThread(data,30).start();  
  9.        new ReaderThread(data,15).start();  
  10.        new ReaderThread(data,10).start();  
  11.        new ReaderThread(data,5).start();  
  12.        new WriteThread(data,"abcdefghijklmnopqrstuvwxyz").start();  
  13.        new WriteThread(data,"ABCDEFGHIJKLMNOPQRSTUVWXYZ").start();  
  14.     }  
  15. }   


執行結果 :
Thread-6 writes a success!
Thread-7 writes A success!
Thread-6 writes b success!
Thread-5 reads bbbbbbbbbb for 1times/5
Thread-1 reads bbbbbbbbbb for 1times/25
Thread-3 reads bbbbbbbbbb for 1times/15
Thread-4 reads bbbbbbbbbb for 1times/10
Thread-2 reads bbbbbbbbbb for 1times/30
Thread-0 reads bbbbbbbbbb for 1times/20
Thread-7 writes B success!
Thread-6 writes c success!
Thread-3 reads cccccccccc for 2times/15
Thread-5 reads cccccccccc for 2times/5
Thread-7 writes C success!
...(以下省略)...


補充說明 :
* 利用同時 “讀取” 不會衝突的特性, 提高程序的性能 : Read-Write Lock pattern 利用了 “讀取操作” 不會改變物件狀態的行為, 所以不需要進行共享互斥, 以提高程序的性能.
* 適合讀取操作頻繁 : 單存使用 Single Thread Execution Pattern, 就連 “讀取操作” 也進行共享互斥, 所有操作一次都只有一個線程進行, 雖然也可以完成 Read-Write Lock 的要求, 但在讀取頻繁的操作下, Read-Write Lock 所表現的性能相對會比較好.
* 鎖定的意義 : 使用 synchronized 可以獲取物件的鎖定, Java 程序的每一個物件各有一個鎖定, 同一個物件的鎖定無法由兩個或以上的線程獲取. 這是制定在Java語言規定裡, 實現在Java 的執行環境的機制. 因為是一開始就提供的功能, 所以我們稱為物理性的鎖定. Java 程序無法去改變這種鎖定行為. 相對的這章的 “讀取鎖定” 與 “寫入鎖定” 與使用 synchronized 獲取鎖定的意義上並不相同. 這不是Java 語言制定的機制, 而是由程序員自己實現的. 這就是所謂邏輯上的鎖定. 當程序員改寫 ReadWriteLock 類時, 就可以改變鎖定的機制. 
This message was edited 4 times. Last update was at 05/01/2010 16:22:52

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...