3

使用模式源于以下原因:

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

上面的模式正确吗?

问题是如果读者是可重入的,即多次锁定读,则释放代码不起作用,读者在获得写锁的那一行挂起。

4

5 回答 5

9

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

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

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

于 2013-01-29T11:40:11.780 回答
4

你的使用模式是错误的:阅读器必须只使用读锁;作家也一样。语义是这样的:只要写锁是空闲的,许多读者可以一次获得读锁;只有在没有获取其他锁(读取或写入)时,writer 才能获取写入锁。

在您的编写器代码中,您尝试在仍持有写锁的同时获取读锁;对于阅读器代码也是如此。

于 2013-01-11T14:12:55.743 回答
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 时在线程之间产生任何争用。

于 2013-01-31T20:43:14.243 回答
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 reader 和 writer 来做同样的事情)

于 2013-01-30T16:04:28.410 回答
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 ();
        }
    }
}
于 2013-02-04T18:36:49.627 回答