1

Javaマルチスレッドの問題があります。2つのスレッドがmethodA()にアクセスしており、その中にはforループがあり、ループ内ではmethodB()が呼び出されます。メソッドAはスレッド名ロックを使用してロックする必要があり、メソッドBはメソッドBが操作​​するオブジェクトIDでロックする必要があります。以下のコードを確認してください。

現在のコード

        private static final ConcurrentHashMap<Object, Object> LOCKS = new ConcurrentHashMap<Object, Object>();   
        private void methodA(){
         LOCKS.putIfAbsent(Thread.currentThread().getName(), new Object()))  
         synchronized (LOCKS.putIfAbsent(Thread.currentThread().getName(), new Object()))        {                
               for(loop through all objects) {
                       methodB(Object1);
               }
         }
        }

    private void methodB(Object1 object1) {      
      LOCKS.putIfAbsent(object1.getObjectId(), new Object()))    
      synchronized(LOCKS.putIfAbsent(object1.getObjectId(), new Object())){         
         //<Work on object1>
      }   
    }

2つの異なるスレッドがmethodA()に並列アクセスできるようにするために上記のコードを実行しましたが、methodB()(methodA()によって呼び出される)で一度に同じObject1で動作しないようにする必要があります。すなわち; スレッドAとスレッドBが同時にmethodA()にアクセスするようにしたいのですが、これは'for'ループ内のすべてのオブジェクトをループし、methodB()を呼び出すことによってそれぞれを操作しますが、スレッドAとBは一度に同じオブジェクトインスタンスに作用します。したがって、オブジェクトインスタンスIDに基づいてmethodB()をロックする上記のコード。

改善が必要です。

上記のコードで、スレッドAとスレッドBがmethodB()に到達し、両方が同じオブジェクト'obj1'で動作することを検出した場合、上記のコードでは、スレッドAが待機するか、スレッドBがもう一方を待機します。最初にmethodB()に到達してロックした人に応じて1つ終了します。

しかし、スレッドAが最初にロックを取得してmethodB()を実行すると、「obj1」の処理が完了するまでに9時間かかる場合を想像してみてください。この場合のスレッドBは、methodB()を実行して「obj1」を処理する機会を得る前に、9時間全体を待機する必要があります。

私はこれが起こらないようにしたい。スレッドBは、methodB()が'obj1'の名前でロックされていることを検出すると、スレッドAは他のオブジェクトのロックと処理を試みるために先に進む(そして後でobj1に戻る)必要があります。すなわち; オブジェクトのリストにあるobj1、obj2などの'for'ループ内の他のオブジェクトの処理を試みる必要があります。

この「待機なしのロック」問題を解決するための入力はありがたいです。

助けてくれてありがとう。

答えを改善するためのいくつかの説明。

  1. methodA()とmethodB()の両方が同じクラスにあります。methodB()はObjectクラスにありません。
  2. 実際、スレッドAとスレッドBは、AとBを含む多くのメソッドを呼び出すタイマースレッドです。したがって、スレッドレベルのロック(スレッドは15分ごとに呼び出され、methodA()の最初の実行が前に完了しない可能性があるため)それへの2回目の呼び出し)。
  3. methodB(Obj1)は常にObject1パラメータを取り、それにロックする必要があります。その理由は、このクラスには、Object1パラメーターも取り込むmethodC(Obj1)やmethodD(Obj1)などの他のメソッドがあるためです。これらのメソッドは、Object1の同じインスタンスに対して同時に実行されるべきではありません。したがって、Object1パラメータのロックが必要です。
  4. methodB(Obj1 obj)がobj1のスレッドA()によってすでにロックされていることを検出したスレッドBは、別のオブジェクト、たとえばobj2を使用してmethodB()を再度呼び出す必要があります。他の人と一緒に終了すると、obj1に戻るはずです。
4

4 に答える 4

5

あなたができる最善のことは、物事をシンプルに保つことです。

メソッド A は、スレッド名ロックを使用してロックする必要があります

共有オブジェクトをロックすることだけが理にかなっています。スレッド ローカル ロックをロックしても意味がありません。

synchronized(LOCKS.putIfAbsent(object1.getObjectId(), new Object()))

nullこれは、最初の実行時に NullPointerExceptionを返し、スローします。


コードを次のように置き換えます

private void methodA(){  
    List<Object1> objects = new ArrayList<>(this.objectList);
    while(true) {
       for(Iterator<Object1> iter = objects.iterator() : objects)
          if(object1.methodB())
             iter.remove();
       if(objects.isEmpty()) break;
       Thread.sleep(WAIT_TIME_BEFORE_TRYING_AGAIN);
    }
}

// in class for Object1
final Lock lock = new ReentrantLock();

public boolean methodB() {          
    if (!lock.tryLock()) 
        return false;
    try {
       // work on this
       return true;
    } finally {
       lock.unlock();
    }
}

ロックできないオブジェクトの処理方法に応じて、それらをバックグラウンドの ExecutorService に追加できます。methodA で、これが失敗した残りのすべてのオブジェクトを繰り返し呼び出すことができます。

理想的には、ロックされた時間を最小限に抑えるか、ロックの必要性を完全になくす方法を見つけるでしょう。たとえば、AtomicReference や CopyOnWriteArrayList などのクラスは、スレッド セーフでロックレスです。

于 2012-08-09T11:31:07.083 に答える
1

私はJavaの専門家ではありませんが、同期ではこれを達成できないと思います。独自のロックを行う必要があると思います。

java.util.concurrent.locks.ReentrantLock を作成すると、まだロックされていない場合は、tryLock を使用してロックに入ることができます。methodA は、どの methodB 呼び出しが成功したか、またはロックできなかったためにキャンセルされたかを知る必要があります。したがって、メソッド A でロック処理を行うことができ、そこで完全に制御できます。または、メソッド B でロックを行うこともできますが、メソッド B がその作業を行った場合、またはロックを取得できなかった場合にメソッド A にシグナルを返すために、何らかの戻り値または例外処理が必要です。

もちろん、既に行ったオブジェクトまたはまだ作業が必要なオブジェクトのリストを methodA に保持する必要もあります。

于 2012-08-09T11:59:36.710 に答える
0

1 つのスレッド パスが、パススルー上のオブジェクトを時折「見逃す」場合は問題になりますか? そうでない場合:

すべてのオブジェクトをロック可能なキュー スタイルのコンテナーに格納します。スレッド A、B などに 1 つをポップさせ、メソッドを呼び出してから、スレッドを押し戻します。スレッドが同じオブジェクトを同時に操作することはできません。唯一のロックはコンテナーのプッシュ/ポップであり、スレッドは長時間ブロックする必要はありません。

..またはそのようなもの。私は常に複雑なロック方式を避けようとしています - それらは常に失敗しているように見えます :(

于 2012-08-09T13:18:34.517 に答える
0

別のアプローチを提案します。メソッドを直接呼び出す代わりに、コマンド オブジェクトをキューに入れ、スレッド (またはエグゼキューター) にコマンドを処理させます。

コマンドがキューに表示されたら、ロックの取得を試みます。それでもうまくいかない場合は、コマンドをキューの最後に追加します。これにより、最終的にコマンドが再試行されるようになります。

欠点: コマンドを実行しようとするたびにスレッドがロックされていると、コマンドが無期限に延期される可能性があります。

ここでの解決策は、必要なものだけをロックするようにすることです。そうすれば、「ああ、これはロックされています」と表示された場合、誰かがすでにタスクに取り組んでいることがわかり、コマンドを簡単に忘れることができます (-> 作業を 2 回実行しないでください)。

于 2012-08-09T13:20:19.517 に答える