5

インスタンス メソッドと静的メソッドの同期について混乱しています。次のようにスレッドセーフなクラスを書きたい:

public class safe {

  private final static ConcurrentLinkedQueue<Object> objectList=
      new ConcurrentLinkedQueue<Object>();

  /**
   * retrieves the head of the object and prints it
   */
    public synchronized static  void getHeadObject() {
      System.out.println(objectList.peek().toString());

    }

    /**
     * creates a new object and stores in the list.
     */
    public synchronized void addObject() {
      Object obj=new Object();
      objectList.add(obj);

    }
}

静的メソッドで同期すると safe.class ロックでロックされ、インスタンス メソッドで同期するとこれでロックされるため、矛盾した状態になります。

以下のコード スニペットで一貫した状態を実現したい場合、どのように実現できますか?

4

3 に答える 3

2

まず、ConcurrentLinkedQueue は明示的な同期を必要としません。この回答を参照してください。

次に、アクセスしているオブジェクトをいつでも同期できます。

public class safe {

      private final static ConcurrentLinkedQueue<Object> objectList=
          new ConcurrentLinkedQueue<Object>();

      /**
       * retrieves the head of the object and prints it
       */
     public static  void getHeadObject() {
         synchronized(objectList){
          System.out.println(objectList.peek().toString());
         }

     }

        /**
         * creates a new object and stores in the list.
         */
     public void addObject() {
          Object obj=new Object();
       synchronized(objectList){
          objectList.add(obj);
       }

     }
}
于 2012-06-25T20:47:10.460 に答える
1

いくつかのコメント:

  • Java 規則:
    • クラス名は CamelCase にする必要があります (つまりSafe、 ではなくクラスを呼び出しますsafe)
    • staticsynchronizedメソッド宣言の前に来る
    • staticfinalフィールド宣言の前に来る
  • 他の人がすでに言ったように、ConcurrentLinkedQueueすでにスレッドセーフであるため、あなたが示した例では同期の必要はありません。
  • 静的メソッドと非静的メソッドを混在させると、奇妙に見えます。
  • 実際のユースケースがより複雑で、アトミック操作を実行するメソッドが必要であると仮定すると、指摘したように、2 つの同期メソッドが同じモニターで同期しないため、コードは機能しません。
public static synchronized getHeadObject(){} //monitor = Safe.class
public static synchronized addObject(){} //monitor = this

したがって、特定の質問に答えるために、別の静的オブジェクトをロックとして使用できます。

public class Safe {

    private static final ConcurrentLinkedQueue<Object> objectList =
            new ConcurrentLinkedQueue<Object>();
    // lock must be used to synchronize all the operations on objectList
    private static final Object lock = new Object();

    /**
     * retrieves the head of the object and prints it
     */
    public static void getHeadObject() {
        synchronized (lock) {
            System.out.println(objectList.peek().toString());
        }
    }

    /**
     * creates a new object and stores in the list.
     */
    public void addObject() {
        synchronized (lock) {
            Object obj = new Object();
            objectList.add(obj);
        }
    }
}
于 2012-06-26T09:18:22.763 に答える
1

編集:私はあなたがQueue<Object> objectList代わりに意味したと仮定していますConcurrentLinkedQueue<Object> objectList. ConcurrentLinkedQueue<Object>はすでにすべてのスレッド セーフを行っています。つまり、objectList.peek()競合状態を心配することなく、必要なだけ呼び出すことができます。これは、マルチスレッド プログラムを開発している場合には便利ですが、スレッド セーフについて学ぶにはあまり適していません。

synchronized一度にオブジェクトの 1 つのインスタンスに対して 1 つのスレッドが動作していると仮定すると、メソッドは である必要はありませんが、すべてが同じ静的クラス変数を参照するクラスの複数のインスタンスが必要な場合は、クラスをsynchronized上書きする必要があります。次のような変数:

public static void getHeadObject() {
    synchronized(safe.objectList) {
        System.out.println(objectList.peek().toString());
    }
}

これにより がロックobjectListされ、プログラムが同期ブロック内に入るとすぐに、他のスレッドで読み取りまたは書き込みができなくなります。他のすべてのメソッドについても同じことを行いますsynchronized

ノート:

ただし、単純な get 操作を 1 回しか実行していないため、競合状態では のいずれかの値または別の値を取得するため、List.peek()を介して同期する必要はありません。競合状態の問題は、複数の複雑な読み取り/書き込み操作が実行され、それらの間で値が変化する場合です。objectListList

たとえばPairIntPairInt.xPairInt.yフィールドを持つクラスがあり、 という制約がx = 2yあり、やりたかった場合

System.out.println(myIntPair.x.toString() + ", " + myIntPair.y.toString());

同時に別のスレッドが and の値を更新してxいました。y

myIntPair.y = y + 3;
myIntPair.x = 2 * y;

myIntPairまた、読み取りスレッド間で書き込みスレッドが変更され、次のような出力が得られる場合がmyIntPair.x.toString()あります。これは、プログラムがクラッシュする可能性があるという前提で操作している場合を意味します。myIntPair.y.toString()(10, 8)x == 2 * y

その場合、読み取りには を使用する必要がありますが、キュー内で変更されずに追加または削除される単純synchronizedなものなどのより単純なものの場合、ほとんどの場合、 を削除できます。実際、 、、などについては、単純な読み取りの条件を削除する必要があります。peek()objectsynchronizedstringintboolsynchronized

ただし、書き込みは、synchronized明示的にスレッド セーフではない操作、つまりすでに Java によって処理されている操作に対して常に行う必要があります。そして、複数のリソースを取得するか、リソースに複数行のロジックを実行するため、リソースが操作全体で同じままであることを要求するとすぐに、使用する必要があります synchronized

于 2012-06-25T20:39:29.470 に答える