使用模式产生于以下原因:
如果条件不存在,我需要读取线程来等待数据。
读锁不支持条件,因此应该从写锁中获取条件。
由于读线程会等待条件,所以它也应该获取写锁定等待。
我在课堂上有以下锁定定义:
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();
}
上面的模式是否正确?
问题在于,如果读者是可重入的,即多次锁定读取,则释放代码不起作用,并且读取器在获取写锁定的行上挂起。
答案 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 ();
}
}
}