在从作者到读者的信号传递过程中正确使用ReentrantReadWriteLock?

时间:2013-01-11 14:04:36

标签: java multithreading concurrency reentrantreadwritelock

使用模式产生于以下原因:

  1. 如果条件不存在,我需要读取线程来等待数据。

  2. 读锁不支持条件,因此应该从写锁中获取条件。

  3. 由于读线程会等待条件,所以它也应该获取写锁定等待。

  4. 我在课堂上有以下锁定定义:

    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();
    }
    

    上面的模式是否正确?

    问题在于,如果读者是可重入的,即多次锁定读取,则释放代码不起作用,并且读取器在获取写锁定的行上挂起。

5 个答案:

答案 0 :(得分:8)

这听起来像是一个经典的生产者/消费者模式,所以我建议您为此目的查看现有的数据结构,例如BlockingQueue实现。

队列中的生产者线程put()数据,来自队列的消费者线程take()数据。

手动同步/锁定应始终是最后的手段。

答案 1 :(得分:3)

您的使用模式错误:读者只能使用读锁定;对于作家来说也一样。语义是这样的:只要写锁定是空闲的,许多读者可以立即获得读锁定;只有在没有获取其他锁定(读取或写入)时,writer才可以获取写入锁定。

在您的编写器代码中,您仍然保持写锁定时尝试获取读锁定;类似的读者代码。

答案 2 :(得分: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)。

< / p>

方法waitForData()将读者变成“编写者”,因为他们锁定了writeLock,导致所有线程(读者和编写者)之间的争用增加。但是,由于假设写入比读取少得多,因此不期望数据在“可用”和“不可用”之间保持快速切换的情况。换句话说,假设写入很少

  • 如果没有可供读取的数据,那么几乎所有读者通常会在一段时间后在方法waitForData()中阻塞,并且在写入一些新数据时会同时通知所有读者。

  • 如果有一些可用的数据可供阅读,那么所有读者只需读取它,而不会在锁定readLock时在线程之间产生任何争用。

答案 3 :(得分: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),你也可以使用   额外的锁定黑白读者和作者做同样的事情)

答案 4 :(得分: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 ();
        }
    }
}