1

「リソース マネージャ」クラスを含むマルチスレッド Java アプリケーションがあります。

このクラスには、初期化パラメーターとして要求できるリソースのリストが提供されます。次に、各ファイルのローカル ファイル システムをチェックし、ローカルであると判断されたファイルをリストに追加します。

クラスがリソース要求を受け取ると、次のいずれかが発生します。

  1. リソースが (リスト内で) ローカルであると判断された場合: リソースが見つかる場所の URI を提供します。

  2. リソースがリモート (リストにない) の場合: ワーカーがリソースを取得するようにスケジュールします。ワーカーは、タスクが完了するとマネージャーに通知し、ローカル リソース リストが更新されます。(リクエストスレッドは待機しません - そこにあるかどうかに関係なく)。

複数のスレッドがリソースを要求できるため、ReadWriteLock を使用してリスト アクセスを調整します。多くのスレッドがリストを同時に読み取ることができ、更新が必要な場合は writeLock が使用されます。

問題は、特定のリモート リソースのバックグラウンド ワーカーをスケジュールすることです。複数のスレッドが同じリソースのワーカーをスケジュールする場合、不必要なオーバーヘッドが必要になります (重複したタスクがこのケースをチェックするために完全に実行されない場合でも)。可能な限り最高の効率を達成するために、次の実装について疑問に思っています。

private final ReadWriteLock lock = new ReentrantReadWriteLock();

//assume each resource object has a volatile boolean "scheduled"
//with isScheduled() and setSheduled() setter/getter;

//assume the resource list is thread safe

public URI requestResource(Resource theResource){
  URI resourceId = null;
  //many threads can enter here 
  lock.readLock().lock();
  try{
    //if our resource is not in the list
    if(!localResouces.contains(theResource)){
       //double-check idiom...does it work here?
       //if the resource isn't scheduled
       if(!theResource.isScheduled()){
          //only one thread may enter here
          synchronized(this){
            if(!theResource.isScheduled()){
               //schedule task here...
               theResource.setScheduled(true);                                      
            }
          }  
       }
    } else {
       //its local, return the location
       resouceId = theResource.getURI();
    }
  } finally {
    lock.readLock().unlock();
  }
//requesting object will deal with null value;
return resouceId;
}

ワーカーが終了すると、次のようになります。

public void update(Resource theResource){
  //ensures no threads in the read block
  lock.writeLock().lock();
  try {
      //update the list (would check result IRL, and not add if problem found)
      localResources.add(theResource);
      //set the scheduled field
      theResource.setScheduled(false); 
  } finally {
       lock.writeLock().unlock();
  }
}

繰り返しますが、効率を最大化したいと思います。このケースに一致する例が見つかりませんでした。つまり、一般的な操作に高いスループットを許可しながら、最小限のブロッキング/オーバーヘッドでタスクをスケジュールできるようにします。

このアプローチにはどのような問題がありますか? 最初のメソッドは読み取りロックと同期の両方を取得する必要がありますが、isScheduled のチェックは読み取りブロック内にカプセル化されているため、更新メソッドは書き込みロックを取得するだけで済みます。これはスレッドセーフなスケジューリングとデータアクセスを提供しますか?

編集:

上記の方法をテストしたところ、正しい動作が見られました。これが実際に「スレッドセーフ」であるかどうかはまだわかりません。

4

1 に答える 1

1

私はそれをこのようにするかもしれません:

class Resource

    Uri localUri;
    volatile int state; // REMOTE, FETCHING, LOCAL

    Resource()
        if local
            localUri = ...;
            state = LOCAL;
        else
            state = REMOTE

    URI requestResource()

        if(state==LOCAL)  // volatile read
            return localUri;
        if(state==FETCHING)
            return null;

        synchronized(lock)
            if(state==LOCAL)
                return localUri;
            if(state==FETCHING)
                return null;

            // REMOTE, and it's my job to initiate fetching
            state=FETCHING;
            // do actaul fetching outside synchronized block

        schedule fetching...

    void onFetchingDone()

        synchronized(lock)

            if error
                state=REMOTE; // to retry.
                // if we consider error unrecoverable,
                // we need an ERROR state.
            else
                ...
                loalUri = ...;
                state = LOCAL;  // volatile write
于 2013-02-18T22:54:30.973 に答える