【问题标题】:Correct usage of ReentrantReadWriteLock while signalling from writer to reader?从写入器向读取器发出信号时正确使用 ReentrantReadWriteLock?
【发布时间】:2013-01-11 14:04:36
【问题描述】:

使用模式源于以下原因:

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

在我的 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 并发 Queues 之一来解决您的问题(这看起来像是典型的生产者/消费者问题)。
  • @ortang 队列不匹配。
  • @Dims 似乎 ReentrantReadWriteLock 也不匹配。你没有指定你想做什么......

标签: java multithreading concurrency 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。 waitawait 函数的目的是它们可以通过来自另一个线程的信号唤醒。你的代码会浪费时间。
    • 我编辑了我的答案以使用租户锁。我个人认为线程。 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 ();
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-12-09
      • 1970-01-01
      • 2012-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多