web-dev-qa-db-ja.com

Javaクリティカルセクションでは、何を同期すべきですか?

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

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

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

70
lindelof

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

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

そして:

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

doまったく同じこと。コードの読みやすさとスタイルを除いて、どちらにも優先度はありません。

メソッドまたはコードのブロックを同期する場合、whyを行うことが重要であり、どのオブジェクトを正確にロックし、目的をロックします。

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

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

コンカレントプログラミングについての知識を深めることをお勧めします。舞台裏で何が起こっているかを正確に把握できれば、大いに役立ちます。 Javaでの並行プログラミング 、このテーマに関するすばらしい本をチェックしてください。サブジェクトをすばやく見たい場合は、 Java Concurrency @ Sun をチェックしてください。

46
Yuval Adam

以前の回答者が指摘したように、スコープが制限されたオブジェクトで同期することをお勧めします(言い換えれば、最も制限の厳しいスコープを選択して使用します)。特に、thisで同期するクラスのユーザーにロックの取得を許可するつもりでない限り、これは悪い考えです。

ただし、Java.lang.Stringで同期することを選択した場合、特にいケースが発生します。文字列はインターンすることができます(実際にはほとんどの場合インターンされます)。つまり、同じ内容の各文字列-ENTIRE JVMで-は、舞台裏で同じ文字列であることがわかります。つまり、文字列を同期すると、同じコンテンツの文字列もロックする別の(完全に異なる)コードセクションが実際にコードもロックします。

私はかつて本番システムのデッドロックをトラブルシューティングし、(非常に痛いほど)内容が両方とも"LOCK"であるStringのインスタンスでそれぞれ同期する2つの完全に異なるオープンソースパッケージのデッドロックを追跡しました。

65
Jared

thisでの同期を避けるようにします。これにより、そのオブジェクトへの参照を持っている外部からのすべてのユーザーが私の同期をブロックできるようになるためです。代わりに、ローカル同期オブジェクトを作成します。

public class Foo {
    private final Object syncObject = new Object();
    …
}

これで、ロックを「盗む」ことを恐れずに、そのオブジェクトを同期に使用できます。

39
Bombe

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

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

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

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

6
Kent Lai

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

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

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

4
Electric Monk

個人的には、thisで同期することは決して、またはめったに正しくないと主張する答えは誤っていると思います。 APIに依存すると思います。クラスがスレッドセーフな実装であり、ドキュメント化されている場合は、thisを使用する必要があります。同期がクラスの各インスタンスをパブリックメソッドの呼び出しでスレッドセーフとして全体を作成しない場合、プライベート内部オブジェクトを使用する必要があります。再利用可能なライブラリコンポーネントoftenは前者のカテゴリに分類されます。外部同期でAPIをラップすることをユーザーに許可しない場合は、慎重に検討する必要があります。

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

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

private Object mutex=new Object();

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

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

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

4
Lawrence Dol

Javaの同期は、多くの場合、同じインスタンスでの操作の同期を伴います。thisは異なるインスタンスメソッド間で自動的に利用可能な共有参照であるため、thisクラスの(またはセクション)。

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

とにかく、暗黙の(同期メソッド)または明示的なsynchronized(this)は、Javaライブラリでも使用されます。これは良いイディオムです。最初に選んだ。

2
eljenso

同期する対象は、このメソッド呼び出しと競合する可能性のある他のスレッドが同期できるものに依存します。

thisが1つのスレッドのみが使用するオブジェクトであり、スレッド間で共有される可変オブジェクトにアクセスしている場合、そのオブジェクトを介して同期することをお勧めします-thisでの同期は、その共有オブジェクトを変更する別のスレッドがthisさえも知らないが、そのオブジェクトは知っている。

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

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

1

ベストプラクティスは、ロックを提供するためだけにオブジェクトを作成することです。

_private final Object lock = new Object();

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

これを行うことで、呼び出しコードが意図しないsynchronized(yourObject)行によってメソッドをデッドロックすることはありません。

上記の詳細でこれを説明した@jaredと@ yuval-adamへのクレジット

私の推測では、チュートリアルでthisを使用する人気は、初期のSun javadocから来たものだと思います。 https://docs.Oracle.com/javase/tutorial/essential/concurrency/locksync.html

0
epox

ほとんどすべてのブロックはこれで同期しますが、これには特別な理由がありますか?他の可能性はありますか?

この宣言はメソッド全体を同期します。

private synchronized void doSomething() {

この宣言は、メソッド全体ではなくコードブロックの一部を同期しました。

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

Oracleドキュメントから ページ

これらのメソッドを同期させると、2つの効果があります。

最初に、同じオブジェクトで同期メソッドを2回呼び出すと、インターリーブすることはできません。 1つのスレッドがオブジェクトの同期メソッドを実行している場合、同じオブジェクトの同期メソッドを呼び出す他のすべてのスレッドは、オブジェクトで最初のスレッドが完了するまでブロック(実行を中断)します。

他の可能性はありますか?同期するオブジェクトのベストプラクティスはありますか? (Objectのプライベートインスタンスなど)

同期には多くの可能性と代替手段があります。高レベルの並行性を使用してコードスレッドを安全にできます APIs (JDK 1.5リリース以降で利用可能)

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

詳細については、以下のSEの質問を参照してください。

同期とロック

Javaでsynchronized(this)を避けますか?

0
Ravindra babu