75

Java では、コード内でクリティカル セクションを宣言する慣用的な方法は次のとおりです。

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

ほとんどすべてのブロックが で同期されます thisが、これには特別な理由がありますか? 他の可能性はありますか?同期するオブジェクトに関するベスト プラクティスはありますか? Object( ?のプライベート インスタンスなど)

4

11 に答える 11

73

以前の回答者が指摘したように、スコープが制限されたオブジェクトで同期することをお勧めします (つまり、回避できる最も制限されたスコープを選択し、それを使用します) this。クラスのユーザーがロックを取得できるようにするつもりです。

ただし、java.lang.String. 文字列はインターンすることができます (実際にはほとんど常にインターンされます)。つまり、内容が等しい文字列 ( JVM 全体) は、裏では同じ文字列であることがわかります。つまり、任意の文字列を同期すると、同じ内容の文字列もロックする別の (完全に異なる) コード セクションが、実際にはコードもロックします。

私はかつて実稼働システムでのデッドロックのトラブルシューティングを行っていましたが、(非常に痛ましいことに) デッドロックを追跡して、2 つの完全に異なるオープン ソース パッケージを追跡しました"LOCK"

于 2009-01-13T16:12:21.980 に答える
51

まず、次のコードスニペットは同一であることに注意してください。

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

と:

public synchronized void foo() {
    // do something thread-safe
}

まったく同じことをします。コードの可読性とスタイルを除いて、どちらも優先されません。

メソッドまたはコードのブロックを同期するときは、なぜそのようなことをしているのか、どのオブジェクトを正確にロックしているのか、そしてどのような目的であるのかを知ることが重要です。

また、次の例のように、要求しているモニター(つまり同期されたオブジェクト)が必ずしもそうではないコードのブロックをクライアント側で同期したい場合があることにも注意してくださいthis

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

並行プログラミングについてもっと知識を身につけることをお勧めします。舞台裏で何が起こっているのかを正確に理解すれば、それはあなたに大いに役立つでしょう。このテーマに関するすばらしい本であるJavaでの並行プログラミングをチェックする必要があります。主題について簡単に詳しく知りたい場合は、Java Concurrency@Sunをチェックしてください。

于 2009-01-06T11:47:43.880 に答える
6

java.util.concurrent.locks.ReadWriteLockとして見つかったJavaで利用可能なReadWriteLocksもあることを強調するだけです。

ほとんどの使用法では、ロックを「読み取り用」と「更新用」に分けています。単に同期されたキーワードを使用する場合、同じメソッド/コードブロックへのすべての読み取りは「キューに入れられます」。一度に1つのスレッドのみがブロックにアクセスできます。

ほとんどの場合、単に読書をしているのであれば、並行性の問題について心配する必要はありません。同時更新(データの損失につながる)または書き込み中の読み取り(部分的な更新)について心配する必要があるのは、書き込みを行っているときです。

したがって、マルチスレッドプログラミング中は、読み取り/書き込みロックの方が理にかなっています。

于 2009-01-06T14:41:41.487 に答える
4

ミューテックスとして機能できるオブジェクトで同期する必要があります。現在のインスタンス(この参照)が適切である場合(たとえば、シングルトンではない場合)、Javaの場合と同様に、任意のオブジェクトがMutexとして機能する可能性があるため、これを使用できます。

また、これらのクラスのインスタンスがすべて同じリソースにアクセスする必要がある場合は、複数のクラス間でMutexを共有することもできます。

それはあなたが働いている環境とあなたが構築しているシステムのタイプに大きく依存します。私が見たほとんどのJavaEEアプリケーションでは、実際には同期の必要はありません...

于 2009-01-06T11:37:20.923 に答える
4

個人的には、同期することが決してまたはめったに正しくないと主張する答えthisは誤った方向に進んでいると思います。API次第だと思います。クラスがスレッドセーフな実装であり、それを文書化する場合は、を使用する必要がありますthis。同期によって、クラスの各インスタンスがパブリックメソッドの呼び出しでスレッドセーフにならない場合は、プライベート内部オブジェクトを使用する必要があります。再利用可能なライブラリコンポーネントは、多くの場合、前者のカテゴリに分類されます。ユーザーがAPIを外部同期でラップすることを禁止する前に、慎重に検討する必要があります。

前者の場合、を使用thisすると、複数のメソッドをアトミ​​ックに呼び出すことができます。1つの例はPrintWriterで、複数の行を出力し(たとえば、コンソール/ロガーへのスタックトレース)、それらが一緒に表示されることを保証します。この場合、同期オブジェクトを内部的に非表示にするという事実は非常に苦痛です。もう1つのそのような例は、同期されたコレクションラッパーです。反復するには、コレクションオブジェクト自体で同期する必要があります。反復は複数のメソッド呼び出しで構成されているため、内部で完全に保護することはできません。

後者の場合、プレーンオブジェクトを使用します。

private Object mutex=new Object();

ただし、ロックが「java.lang.Object()のインスタンス」であると言う多くのJVMダンプとスタックトレースを見てきましたが、他の人が示唆しているように、内部クラスを使用する方が役立つ場合が多いと言わざるを得ません。

とにかく、それは私の2ビットの価値があります。

編集:もう1つ、同期するときは、メソッドを同期しthis、メソッドを非常に細かく保つことを好みます。より明確で簡潔だと思います。

于 2009-01-15T07:35:12.037 に答える
2

Java での同期には、多くの場合、同じインスタンスでの操作の同期が含まれます。その後の同期は、クラス内の異なるインスタンス メソッド (またはセクション) 間で自動的に使用できる共有参照であるthisため、非常に慣用的です。this

たとえば、プライベート フィールドを宣言して初期化することにより、ロック専用の別の参照を使用するObject lock = new Object()ことは、私が決して必要とせず、使用したこともありません。オブジェクト内の2つ以上の同期されていないリソースで外部同期が必要な場合にのみ役立つと思いますが、そのような状況をより単純な形式にリファクタリングすることを常に試みます.

とにかく、暗黙的 (同期メソッド) または明示的synchronized(this)が多く使用され、Java ライブラリーでも使用されます。これは適切なイディオムであり、該当する場合は常に最初の選択肢として使用する必要があります。

于 2009-01-06T12:26:53.520 に答える
1

何を同期するかは、このメソッド呼び出しと潜在的に競合する可能性のある他のどのスレッドが同期できるかによって異なります。

が 1 つのスレッドのみで使用されるオブジェクトthisであり、スレッド間で共有される変更可能なオブジェクトにアクセスしている場合、そのオブジェクトを同期することが適切な候補です。thisその共有オブジェクトを変更する別のスレッドでさえも変更されない可能性があるため、同期しても意味がありません。知っthisていますが、そのオブジェクトを知っています。

一方、this多くのスレッドがこのオブジェクトのメソッドを同時に呼び出す場合、たとえばシングルトンにいる場合、同期は理にかなっています。

メソッドの実行中ずっとロックを保持しているため、同期化されたメソッドは多くの場合、最適なオプションではないことに注意してください。時間がかかるがスレッド セーフな部分と、それほど時間はかからないスレッド セーフでない部分が含まれている場合、メソッドを介した同期は非常に間違っています。

于 2010-06-23T13:43:55.783 に答える