2013-01-11 27 views
1

的使用模式由以下原因產生:正確使用ReentrantReadWriteLock時,從寫入者到讀者的信號?

  1. 我需要讀線程等待數據,如果它不存在與條件。

  2. 讀鎖不支持條件,所以條件應該從寫鎖中獲取。

  3. 既然讀線程會等待條件,它也應該等待寫鎖。

我在課堂以下鎖定義:

private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
protected final Lock readLock = rwl.readLock(); 
protected final Lock writeLock = rwl.writeLock(); 
protected final Condition hasData = writeLock.newCondition(); 

在我寫程序方法,我有以下模式:

try { 
    writeLock.lock();  

    //... 

    if(something_written) { 
     hasData.signalAll(); 
    } 

} 
finally { 
    writeLock.unlock(); 
} 

在我讀法我已經圖案

以下
try { 
    readLock.lock();  

    while(data_absent) { 

     // I need to acquire write lock to wait for condition! 
     try { 

      // first releasing read lock since we can't acquire write lock otherwise 
      // unfortunately this won't release a lock if it was acquired more than once (reentrant) 
      readLock.unlock(); 

      // acquiring write lock to wait it's condition 
      writeLock.lock(); 
      hasData.await(1000, TimeUnit.MILLISECONDS); 
     } 
     finally { 

      // releasing write lock back 
      writeLock.unlock(); 

      // reacquiring read lock 
      // again see note about reentrancy 
      readLock.lock(); 
     } 


    } 

    // reading 

} 
finally { 
    readLock.unlock(); 
} 

是t他的模式正確嗎?

問題是,如果讀者是可重入的,即鎖定讀取不止一次,那麼釋放代碼不起作用,讀者掛在獲取寫鎖定的行。

+2

短短的意見......你錯過了'試穿finally'構建嵌套的鎖定/解鎖。按照慣例,你應該在'try'塊之外「鎖定」。 – mre

+0

爲什麼你獲得'readlock',而你已經獲得了'writelock'(反之亦然)?這不是使用它們的正確方法。看看在[API](http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReadWriteLock.html) –

+1

順便說一句,如果可能的話,我建議使用一個用於解決您的問題的Java併發'隊列'(看起來像一個典型的生產者/消費者問題)。 –

回答

3

您的使用模式錯誤:讀者只能使用讀鎖;作者也一樣。語義是這樣的:只要寫鎖定是空閒的,許多讀者可以一次獲取讀鎖定;只有在沒有獲得其他鎖(讀或寫)的情況下,寫入者纔可以獲取寫鎖。

在您的編寫器代碼中,當您仍然保持寫入鎖定時,嘗試獲取讀取鎖定;類似於讀者代碼。

+0

那麼如何讓讀者等待數據呢? – Dims

+0

閱讀鎖不支持條件,所以唯一的方式來通知讀線程是通過寫鎖定 – Dims

8

這聽起來像一個經典的生產者/消費者模式,所以我建議你看看現有的數據結構爲此目的,如BlockingQueue實現。

生產者線程put()隊列中的數據,消費者線程take()來自隊列的數據。

手動同步/鎖定應始終是最後的手段。

+0

我知道'BlockingQueue'和它不匹配。我是最後的手段,因爲我正在編寫自己的隊列/緩衝區和自定義功能。問題是關於鎖,而不是關於預定義的隊列類。 – Dims

+0

這不是一個答案。您建議絕對使用另一個類,而忽略ReentrantReadWriteLock類的缺點。 – Dims

+2

@Dims巴迪,冷靜下來。根據您的問題中的信息,我提供了我認爲是更好的選擇。如果你有其他限制,你沒有包括在這裏,那麼我不是一個心靈讀者。 – pap

1

我覺得你要做的是讓你的閱讀器等待寫入者寫入,然後返回一些的值。如果沒有價值,你希望讀者線程等待或睡眠。那是對的嗎 ? 如果我的理解是正確的,這裏的單程要做到這一點。

private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
protected final Lock readLock = rwl.readLock(); 
protected final Lock writeLock = rwl.writeLock(); 
protected final Condition hasData = writeLock.newCondition(); 
private HashMap myData = new HashMap(); //example structure to read and write 

private final ReentrantLock dataArrivalLock = new ReentrantLock(); 
private final Condition dataArrivalSignal = dataArrivalLock.newCondition(); 

刻錄機的方法模式:

try { 
    writeLock.lock();  

    //... 
    myData.put("foo","ffoo"); //write something !! 
    if(something_written) { 
     hasData.signalAll(); 
    } 

} 
finally { 
    writeLock.unlock(); 
} 
    try { 
       //signal other threads that data has been put in 
       dataArrivalLock.lock(); 
       dataArrivalSignal.signalAll(); 

      } finally { 
       dataArrivalLock.unlock(); 
      } 

你的閱讀器方法模式

try { 
      boolean gotData = false; 
      while (!gotData) { 
       try { 
        readLock.lock(); 
        if (myData.size() > 0) { 
         gotData = true; 
         //retrieve the data that is written by writer thred!! 
         myData.get("foo"); 
        } 
       } finally { 
        readLock.unlock(); 
       } 
       if(!gotData) { 
//sleep the reader thread for x milliseconds. x depends on your application requirement 
        // Thread.sleep(250); 
        try { 
         //instead of Thread.sleep(), use the dataArrivalLock signal to wakeup 
         dataArrivalLock.lock(); 
         dataArrivalSignal.await(); 
         //based on how the application works a timed wait might be better !! 
         //dataArrivalSignal.await(250); 
        } finally { 
         dataArrivalLock.unlock(); 
        } 
       } 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 

這樣做是讀者線程睡眠直到一些數據被寫入作家線程。

(與其使用的Thread.sleep(250),你也可以使用可能是一個額外的 鎖的B/W讀寫器做同樣的事情)

+0

即使新數據在睡眠狀態下到達,您的線程也會睡250。 「等待」或「等待」功能的目的在於它們可以通過來自另一線程的信號喚醒。你的代碼會浪費時間。 – Dims

+0

我編輯了我的答案以使用租戶鎖定。我個人認爲線程。 sleep()是更簡單的方法(更少的代碼)。根據您的應用程序,您可以校準您的睡眠時間。如果數據持續出現,您可以將其設置爲低至50 ms – Zenil

+0

@Zenil我認爲dataArrival同步邏輯中存在漏洞。讀者線程嘗試獲取數據,而沒有。然後作者放入一些數據,並觸發dataArrivalSignal。然後閱讀器等待信號。它會等到作者再次寫入*時,纔會將第一批數據留在那裏。 – sharakan

2

這是我會在你做情況:

private final ReentrantReadWriteLock rwl   = new ReentrantReadWriteLock(); 
protected final Lock     readLock = rwl.readLock(); 
protected final Lock     writeLock = rwl.writeLock(); 
protected final Condition    hasData  = writeLock.newCondition(); 


public void write() { 

    writeLock.lock(); 
    try { 
     // write data 
     // ... 
     if (something_written) { 
      hasData.signalAll(); 
     } 
    } 
    finally { 
     writeLock.unlock(); 
    } 
} 

// replace Object by something else 
public Object read() throws InterruptedException { 

    Object data = tryRead(); 

    while (data == null) { 
     waitForData(); 
     data = tryRead(); 
    } 

    return data; 
} 

// replace Object by something else 
private Object tryRead() { 

    readLock.lock(); 
    try { 
     Object data = null; 
     // read data 
     // ... 
     // if there no data available, return null 
     return data; 
    } 
    finally { 
     readLock.unlock(); 
    } 
} 

private void waitForData() throws InterruptedException { 

    writeLock.lock(); 
    try { 
     boolean data_available = // check data 
     while (!data_available) { 
      hasData.await(1000L, TimeUnit.MILLISECONDS); 
      data_available = // check data 
     } 
    } 
    finally { 
     writeLock.unlock(); 
    } 
} 


這是典型的ReadWriteLock中的使用情況的相同的行爲是否有可用的數據讀取。如果不存在數據,則讀卡器成爲「寫入器」(在鎖定意義上),並等待一些數據可用。循環重複,直到返回一些可用的數據(或直到發生中斷)。


由於您使用的是ReadWriteLock中,這意味着你期待 更大數量的讀比寫等你選擇,儘量減少讀取線程(在readLock)之間爭論的鎖。

方法waitForData()變爲讀者變成「作家」,因爲它們鎖定在writeLock代替,導致所有線程(讀取器和寫入)之間增加的爭用。然而,由於寫入被認爲是非常罕見比讀,預計不會在數據保持爲「可用」和「不可用」之間切換速度快的情況。換句話說,假設寫是罕見

  • 如果讀取沒有可用的數據,那麼幾乎所有的讀者通常會在一段時間後,該方法waitForData()塊,並都將在被通知同時寫入一些新的數據。

  • 如果是閱讀一些可用的數據,那麼所有的讀者會簡單地讀它不鎖定readLock當創建線程之間的任何競爭。

0

怎麼樣以下辦法(註釋代碼):

public class ReadWrite 
{ 
    private final Lock readLock; 
    private final Lock writeLock; 
    private final Condition condition; 

    { 
     ReadWriteLock rwl = new ReentrantReadWriteLock(); 
     readLock = rwl.readLock(); 
     writeLock = rwl.writeLock(); 
     condition = writeLock.newCondition(); 
    } 

    private Object data; 

    // If data is there, return it, otherwise, return null 
    private Object tryGetData() 
    { 
     readLock.lock(); 
     try 
     { 
      return data; // May return null 
     } 
     finally 
     { 
      readLock.unlock(); 
     } 
    } 

    // Wait for data if necessary and then return it 
    private Object doGetData() throws InterruptedException 
    { 
     writeLock.lock(); 
     try 
     { 
      while (data == null) 
       condition.await(); 

      return data; 
     } 
     finally 
     { 
      writeLock.unlock(); 
     } 
    } 

    // Used by reader, return value cannot be null, may block 
    public Object getData() throws InterruptedException 
    { 
     Object result = tryGetData(); 
     return result == null ? doGetData() : result; 
    } 

    // Used by writer, data may be null 
    public void setData (Object data) 
    { 
     writeLock.lock(); 
     try 
     { 
      Object previousData = this.data; 
      this.data = data; 
      if (previousData == null && data != null) 
       condition.notifyAll(); 
     } 
     finally 
     { 
      writeLock.unlock(); 
     } 
    } 
} 
相關問題