从写入器向读取器发出信号时正确使用 ReentrantReadWriteLock?
Posted
技术标签:
【中文标题】从写入器向读取器发出信号时正确使用 ReentrantReadWriteLock?【英文标题】:Correct usage of ReentrantReadWriteLock while signalling from writer to reader? 【发布时间】:2013-01-11 14:04:36 【问题描述】:使用模式源于以下原因:
如果条件不存在,我需要读取线程来等待数据。
读锁不支持条件,所以条件应取自写锁。
由于读线程会等待条件,它也应该获取写锁来等待。
我在课堂上有以下锁定义:
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
protected final Lock readLock = rwl.readLock();
protected final Lock writeLock = rwl.writeLock();
protected final Condition hasData = writeLock.newCondition();
在我的 writer 方法中,我有以下模式:
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();
上面的模式正确吗?
问题是如果reader是可重入的,即多次锁定read,那么释放代码不起作用,reader在获取写锁的那一行挂起。
【问题讨论】:
只是一些观察...您缺少用于嵌套锁定/解锁的try-finally
构造。按照惯例,您应该在try
块之外使用lock
。
为什么你已经获得readlock
而获得writelock
(反之亦然)?这不是使用它们的正确方法。看看API
顺便说一句,如果可能的话,我建议使用 Java 并发 Queue
s 之一来解决您的问题(这看起来像是典型的生产者/消费者问题)。
@ortang 队列不匹配。
@Dims 似乎 ReentrantReadWriteLock 也不匹配。你没有指定你想做什么......
【参考方案1】:
这听起来像是经典的生产者/消费者模式,因此我建议您为此目的查看现有数据结构,例如 BlockingQueue 实现。
生产者线程put()
队列中的数据,消费者线程take()
队列中的数据。
手动同步/锁定应始终是最后的手段。
【讨论】:
我知道BlockingQueue
,但它不匹配。我是最后的手段,因为我正在编写自己的具有自定义功能的队列/缓冲区。问题是关于锁,而不是关于预定义的队列类。
这不是答案。您建议绝对使用另一个类,而忽略ReentrantReadWriteLock
类的描述缺点。
@Dims Buddy,冷静下来。根据您问题中的信息,我提供了我认为更好的选择。如果您有其他未在此处包含的限制,那么我不是读心者。
@Dims 下定决心。首先,您因为我提出了 ReentrantReadWriteLock 的替代方案而激怒我,现在您说自己它有缺点。是哪条路?你必须使用 ReentrantReadWriteLock 吗?或者你可以使用其他一些同步机制吗?作为记录: BlockingQueue 满足您在问题中指定的所有标准。它可能不是正确的答案,但是您需要为问题添加更多细节。我在您的代码中看到的唯一错误是您应该在 finally 块中切换 writeLock.unlock() 和 readLock.lock() 的顺序。
@Dims 同样。祝你好运!【参考方案2】:
你的使用模式是错误的:阅读器只能使用读锁;作家也一样。语义是这样的:只要写锁是空闲的,许多读者可以一次获得读锁;只有在没有获取其他锁(读取或写入)时,writer 才能获取写入锁。
在您的编写器代码中,您尝试在仍持有写锁的同时获取读锁;阅读器代码也是如此。
【讨论】:
读锁不支持条件,因此向读线程发出信号的唯一方法是通过写锁【参考方案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();
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 时在线程之间产生任何争用。
【讨论】:
【参考方案4】:我认为你想要做的是让你的读者等待作家写,然后返回 some 价值。如果没有价值,您希望您的阅读器线程只是等待或休眠。那是对的吗 ? 如果我的理解是正确的,这是一种方法。
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 读写器来做同样的事情)
【讨论】:
即使新数据在休眠时到达,您的线程也会休眠 250。wait
或 await
函数的目的是它们可以通过来自另一个线程的信号唤醒。你的代码会浪费时间。
我编辑了我的答案以使用租户锁。我个人认为线程。 sleep() 是更简单的方法(更少的代码)。根据您的应用程序,您可以校准您的睡眠时间。如果数据不断出现,您可以将其设置为低至 50 毫秒
@Zenil 我认为您的 dataArrival 同步逻辑中有一个漏洞。 Reader 线程尝试获取数据,但没有。然后 writer 放入一些数据,并触发 dataArrivalSignal。然后阅读器等待信号。它会等到作者再次写入,留下第一部分数据。
@sharakan 这是真的。但假设是编写器线程将始终运行并写入一些东西,并且有 [多个] 读取器线程在等待数据,这意味着其中一个会唤醒。这可以通过定时等待轻松解决,例如:dataArrivalSignal .await(250)(我用注释修改了我的代码)..Dims 可以决定前进的最佳方式。【参考方案5】:
下面的方法怎么样(cmets在代码中):
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 ();
【讨论】:
以上是关于从写入器向读取器发出信号时正确使用 ReentrantReadWriteLock?的主要内容,如果未能解决你的问题,请参考以下文章
AT24C02写一个数据然后读取一个数据是正确的,但是当写入多个数据时,读出数据就不正确,求指教?
从 C++ 向 QML 发出信号以读取 Q_PROPERTY 是同步事件吗?