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

上記のパターンは正しいですか?

問題は、reader が再入可能である場合、つまり 2 回以上読み取りをロックしている場合、解放コードが機能せず、reader が書き込みロックを取得する行でハングすることです。

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

あなたがやろうとしているのは、リーダーがライターが書き込むのを待ってから、何らかの値を返すことだと思います。値がない場合は、リーダー スレッドを待機またはスリープさせます。あれは正しいですか ?私が理解したことが正しければ、それを行う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 リーダーとライターを使用して同じことを行うこともできます)

于 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 に答える