Javaで同期(これ)を避けますか?


381

Javaの同期についての質問がSOに出てくるときはいつでも、synchronized(this)避けなければならないことを非常に熱心に指摘する人もいます。代わりに、プライベートリファレンスのロックが優先されると主張しています。

指定された理由のいくつかは次のとおりです。

私を含む他の人々synchronized(this)は、それが(Javaライブラリーでも)よく使用されるイディオムであり、安全でよく理解されていると主張します。バグがあり、マルチスレッドプログラムで何が行われているかの手掛かりがないため、これを回避することはできません。つまり、該当する場合はそれを使用します。

私は、ロックオンを回避するthisことが望ましい場合に、ロックオンを回避することが望ましい実際の例(foobarのものではない)をいくつか見ることに興味synchronized(this)があります。

したがって、常に避けsynchronized(this)て、プライベートリファレンスのロックに置き換える必要がありますか?


いくつかの詳細情報(回答が提供されると更新されます):

  • インスタンスの同期について話している
  • の暗黙的な(synchronizedメソッド)と明示的な形式のsynchronized(this)両方が考慮されます
  • この件に関してBlochまたは他の当局を引用する場合は、好ましくない部分を省略しないでください(例:効果的なJava、スレッドセーフティの項目:通常はインスタンス自体のロックですが、例外があります)。
  • synchronized(this)提供以外の細かいロックが必要な場合synchronized(this)は適用されないため、問題はありません。

4
また、コンテキストが重要であることも指摘しておきます。「通常はインスタンス自体のロックです」ビットは、ロックをパブリックにするときに、条件付きでスレッドセーフなクラスのドキュメント化に関するセクション内にあります。つまり、この文は、すでにこの決定を行っている場合に適用されます。
Jon Skeet、

内部同期がない場合、および外部同期が必要な場合、ロックはインスタンス自体であることが多いとブロッホは基本的に述べています。では、これが 'this'をロックした内部同期の場合も同様ではないのですか?(ドキュメントの重要性は別の問題です。)
eljenso 2009年

外部オブジェクトのロックは、CPUキャッシュ間で変更および交換するために別のキャッシュラインが必要になる可能性が高いため(MESIFとMOESIを参照)、拡張された粒度と追加のCPUキャッシュおよびバスリクエストオーバーヘッドとの間にはトレードオフがあります。
ArtemGr 2010年

1
ディフェンシブプログラミングの世界では、イディオムではなくコードによってバグを防止すると思います。誰かが「同期はどのように最適化されていますか?」と質問すると、「他の人がイディオムに従っていない場合を除いて、「非常に」ではなく「非常に」と言いたいのです。」
Sgene9

回答:


132

各ポイントを個別にカバーします。

  1. いくつかの邪悪なコードがあなたのロックを盗む可能性があります(これは非常に人気があり、「誤って」バリアントも持っています)

    うっかり心配です。つまり、の使用はthisクラスの公開されたインターフェースの一部であり、文書化する必要があります。ロックを使用する他のコードの機能が必要な場合があります。これはCollections.synchronizedMap(javadocを参照)などにも当てはまります。

  2. 同じクラス内のすべての同期されたメソッドは、まったく同じロックを使用するため、スループットが低下します

    これは過度に単純化した考え方です。取り除くだけでsynchronized(this)は問題は解決しません。スループットを適切に同期するには、さらに検討が必要です。

  3. (不必要に)情報を公開しすぎている

    これは#1のバリアントです。の使用はsynchronized(this)インターフェースの一部です。これを公開したくない、または公開したくない場合は、実行しないでください。


1.「同期」は、クラスの公開されたインターフェースの一部ではありません。2.同意する3. 1.を参照
eljenso 2009年

66
これは、外部コードがクラスの操作に影響を与える可能性があることを意味するため、本質的に同期(this)公開されます。だから、私はあなたがそれをインターフェースとして文書化しなければならないと断言します。
ダロン、

15
同様です。Collections.synchronizedMap()のJavadocを参照してください。返されたオブジェクトは内部でsynchronized(this)を使用しており、コンシューマーはそれを利用して、反復などの大規模なアトミック操作に同じロックを使用することを期待しています。
ダロン、

3
実際、Collections.synchronizedMap()は内部的にsynchronized(this)を使用せず、プライベートな最終ロックオブジェクトを使用します。
Bas Leijdekkers 2016年

1
@Bas Leijdekkers:ドキュメントには、返されたマップインスタンスで同期が行われることが明記されています。興味深いのは、ロックされkeySet()values()いない(それらの)ビューによって返されるビューではなくthis、すべてのマップ操作で一貫した動作を実現するために重要なマップインスタンスです。ロックオブジェクトが変数に分解される理由は、SynchronizedSortedMap元のマップインスタンスをロックするサブマップを実装するためにサブクラスがそれを必要とするためです。
Holger

86

まあ、まずそれが指摘されるべきです:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

意味的には次と同等です:

public synchronized void blah() {
  // do stuff
}

これは使用しない理由の1つsynchronized(this)です。あなたはsynchronized(this)ブロックの周りで何かをすることができると主張するかもしれません。通常の理由は、試してみて、すべての同期のチェックを行うことを避けるためにある、具体的並行性の問題のすべての種類につながる二重にチェックロックの問題だけで、それは比較的簡単なチェックを行うことがいかに困難を示すことを行きます、スレッドセーフ。

プライベートロックは防御メカニズムであり、決して悪い考えではありません。

また、先ほど触れたように、プライベートロックは粒度を制御できます。オブジェクトに対するある操作セットは別の操作セットとはまったく無関係である可能性がありますがsynchronized(this)、それらすべてへのアクセスは相互に除外されます。

synchronized(this) 本当に何もしません。


4
「同期(これ)は本当に何もしません。」OK、それをsynchronize(myPrivateFinalLock)に置き換えます。それは私に何を与えますか?あなたはそれが防御メカニズムであると話します。何に対して保護されていますか?
エルジェンソ2009年

14
外部オブジェクトによる「this」の偶発的な(または悪意のある)ロックから保護されます。
cletus 2009年

14
私はこの答えにまったく同意しません。ロックは常に可能な限り短い時間保持する必要があり、それがまさに、メソッド全体を同期するのではなく、同期されたブロックの周りで「実行」したい理由です。
オリビエ

5
同期されたブロックの外で何かをすることは常に意図的です。重要なのは、ダブルチェックされたロックの問題のように、人々は多くの場合これを間違え、それに気づかないことです。地獄への道は善意で舗装されています。
cletus 2009年

16
私は一般的に「Xは防御メカニズムであり、決して悪い考えではない」と反対しています。このような態度のため、不必要に肥大化したコードがたくさんあります。
finnw 2010

54

synchronized(this)を使用している間、クラスインスタンスをロックとして使用しています。つまり、スレッド1によってロックが取得されている間、スレッド2は待機する必要があります。

次のコードを想定します。

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

変数変更方法1 A及び可変修正方法2 (b)は、二つのスレッドによる同じ変数の同時変更は避けるべきであり、それはあります。一方、しかしスレッド1が変更とスレッド2の変性Bそれは、任意の競合状態なく行うことができます。

残念ながら、上記のコードでは同じ参照をロックに使用しているため、これは許可されません。つまり、競合状態にない場合でもスレッドは待機する必要があり、コードはプログラムの同時実行性を犠牲にします。

解決策は、使用することです2つのに別のロックを2つの異なる変数:

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

上記の例では、よりきめの細かいロック(2つのロックではなく、1つ(変数abに対してそれぞれlockAlockB))を使用しているため、同時実行性が向上していますが、最初の例よりも複雑になりました...


これは非常に危険です。これで、クライアント側(このクラスのユーザー)のロック順序付け要件が導入されました。2つのスレッドがmethod1()とmethod2()を異なる順序で呼び出している場合、それらはデッドロックする可能性がありますが、このクラスのユーザーはこれが事実であることを知りません。
daveb 2009年

7
"synchronized(this)"で提供されない粒度は、私の質問の範囲外です。そして、あなたのロックフィールドは最終的であるべきではありませんか?
eljenso 2009年

10
我々はB.のdavebによって同期ブロックにAで同期ブロックからの呼び出しを実行する必要があり、デッドロックを持っているために、あなたは間違っている...
アンドレアスBakurov

3
この例では、私が見る限りデッドロックはありません。単なる擬似コードであることに同意しますが、java.util.concurrent.locks.ReentrantLockのようなjava.util.concurrent.locks.Lockの実装の1つを使用します
Shawn Vader

15

独断的なルールを盲目的に遵守しないことについては同意しますが、「ロックを盗む」シナリオはあなたにとってそれほど奇妙に見えますか?スレッドが実際にオブジェクトの「外部」(synchronized(theObject) {...})のロックを取得し、同期されたインスタンスメソッドで待機している他のスレッドをブロックする可能性があります。

悪意のあるコードを信じない場合は、このコードがサードパーティからのものである可能性があることを考慮してください(たとえば、ある種のアプリケーションサーバーを開発している場合)。

「偶発的」バージョンの可能性は低いようですが、彼らが言うように、「何かばかげたことを防げば、誰かがもっとばかげたことを発明するでしょう」。

それで、私はそれがクラスに何をするかに依存するという考え方に同意します。


eljensoの最初の3件のコメントを編集:

ロックを盗む問題を経験したことはありませんが、架空のシナリオを以下に示します。

あなたのシステムがサーブレットコンテナであり、私たちが考慮しているオブジェクトがServletContext実装であるとしましょう。getAttributeコンテキスト属性は共有データであるため、そのメソッドはスレッドセーフでなければなりません。そのため、次のように宣言しますsynchronized。また、コンテナーの実装に基づいてパブリックホスティングサービスを提供するとします。

私はあなたの顧客であり、私の「良い」サーブレットをあなたのサイトに配備します。たまたま私のコードにへの呼び出しが含まれていますgetAttribute

別の顧客を装ったハッカーが、自分の悪意のあるサーブレットをサイトに配置します。initメソッドには次のコードが含まれています。

同期(this.getServletConfig()。getServletContext()){
   while(true){}
}

同じサーブレットコンテキストを共有すると(2つのサーブレットが同じ仮想ホスト上にある限り、仕様で許可されます)、私の呼び出しgetAttributeは永久にロックされます。ハッカーが私のサーブレットでDoSを達成しました。

getAttributeサードパーティのコードがこのロックを取得できないため、プライベートロックで同期されている場合、この攻撃は不可能です。

この例は人為的なものであり、サーブレットコンテナの動作の単純化した見方ですが、私見ではそれがポイントを証明しています。

したがって、セキュリティの考慮に基づいて設計を選択します。インスタンスにアクセスできるコードを完全に制御できますか?スレッドがインスタンスに対して無期限にロックを保持するとどうなりますか?


it-depends-on-what-the-class-does:それが「重要な」オブジェクトである場合、プライベート参照をロックしますか?そうでなければ、インスタンスのロックで十分でしょうか?
eljenso、2009年

6
はい、ロックを盗むシナリオは、私にはなかなか難しいものです。誰もがそれについて言及していますが、実際にそれをした人や経験した人はいますか?「誤って」ロックしてはいけないオブジェクトをロックすると、この種の状況に名前が付けられます。それはバグです。修理する。
eljenso

4
また、内部参照のロックは「外部同期攻撃」から解放されません。コードの特定の同期部分が外部イベントの発生を待機していることがわかっている場合(ファイルの書き込み、DBの値、タイマーイベントなど)、おそらくそれもブロックするように配置します。
eljenso

私が若い頃にやったとしても、私はそれらの馬鹿の一人であることを告白させてください。明示的なロックオブジェクトを作成しないことでコードがよりクリーンになると思い、代わりに、モニターに参加する必要がある別のプライベート最終オブジェクトを使用しました。オブジェクト自体が同期していることを知りませんでした。続くhijinxを想像できます...
アランカブレラ

12

状況によります。
共有エンティティが1つだけの場合、または複数の場合。

ここで完全な動作例を見る

小さな紹介。

スレッドと共有可能なエンティティ
複数のスレッドが同じエンティティにアクセスすることは可能です。たとえば、単一のmessageQueueを共有する複数のconnectionThreadsです。スレッドは並行して実行されるため、めちゃくちゃな状況になりかねない別のデータでデータを上書きする可能性があります。
したがって、共有可能なエンティティに一度に1つのスレッドのみがアクセスできるようにする方法が必要です。(並行性)。

Synchronized block
synchronized()ブロックは、共有可能なエンティティへの同時アクセスを保証する方法です。
最初に、小さな例え
が、洗面所の内側に2人のP1、P2(スレッド)、洗面台(共有可能エンティティ)があり、ドア(ロック)があるとします。
今、私たちは一度に一人の洗面器を使いたいと思っています。
アプローチは、ドアがロックされているときにP1によってドアをロックすることです
。P2は、
p1が作業を完了するまで待機します。P1がドアのロックを解除すると、p1だけが洗面台を使用できます。

構文。

synchronized(this)
{
  SHARED_ENTITY.....
}

「this」は、クラスに関連付けられた固有のロックを提供しました(Java開発者は、各オブジェクトがモニターとして機能できるようにObjectクラスを設計しました)。上記のアプローチは、1つの共有エンティティと複数のスレッド(1:N)しかない場合に正常に機能します。N共有可能エンティティーMスレッド ここで、洗面所内に2つの洗面台があり、ドアが1つしかない状況を考えてみます。前のアプローチを使用している場合、p2が外部で待機する間、p1のみが一度に1つの洗面台を使用できます。誰もB2(洗面器)を使用していないので、それは資源の浪費です。 賢明な方法は、洗面所の中に小さな部屋を作り、洗面台ごとに1つのドアを設けることです。このようにして、P1はB1にアクセスでき、P2はB2にアクセスでき、その逆も可能です。
ここに画像の説明を入力してください

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

ここに画像の説明を入力してください
ここに画像の説明を入力してください

スレッドの詳細はこちら ----> こちら


11

これに関するC#とJavaのキャンプでは異なるコンセンサスがあるようです。 私が見たJavaコードの大部分は以下を使用します:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

一方、C#コードの大部分は、間違いなくより安全であることを選択します。

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

C#のイディオムは確かに安全です。前述のように、インスタンスの外部から悪意のある/偶発的なロックへのアクセスを行うことはできません。Javaコードにもこのリスクがありますが、Javaコミュニティは少しずつ安全性は低くなりますが、やや簡潔なバージョンに時間をかけて引き付けられたようです。

これは、Javaに対する発掘を意味するものではなく、両方の言語での私の経験を反映したものです。


3
おそらく、C#はより若い言語であるため、Javaキャンプで判明した悪いパターンと、このようなより良いコードから学んだのでしょうか。シングルトンも少ないですか?:)
ビルK

3
彼彼。かなり本当かもしれませんが、私は餌に上がるつもりはありません!確かに言えることの1つは、C#コードには大文字が多いということです;)
serg10

1
真実ではありません(うまく
表現

7

このjava.util.concurrentパッケージにより、スレッドセーフコードの複雑さが大幅に軽減されました。私は継続する事例証拠しかありませんが、私が見たほとんどの作業はsynchronized(x)、ロック、セマフォ、またはラッチを再実装しているようですが、低レベルのモニターを使用しています。

これを念頭に置いて、これらのメカニズムのいずれかを使用して同期することは、ロックをリークするのではなく、内部オブジェクトで同期することと類似しています。これは、2つ以上のスレッドによってモニターへのエントリを制御する絶対的な確実性があるという点で有益です。


6
  1. 可能であれば、データを不変にします(final変数)
  2. 複数のスレッドにまたがる共有データの変更を回避できない場合は、高レベルのプログラミングコンストラクトを使用してください(たとえば、詳細な LockAPI)

ロックは、共有リソースへの排他的アクセスを提供します。一度に1つのスレッドのみがロックを取得でき、共有リソースへのすべてのアクセスには、最初にロックを取得する必要があります。

インターフェイスReentrantLockを実装する使用するサンプルコードLock

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

同期化に対するロックの利点(これ)

  1. 同期されたメソッドまたはステートメントを使用すると、すべてのロックの取得と解放がブロック構造で発生します。

  2. ロックの実装は、以下を提供することにより、同期されたメソッドとステートメントの使用に追加の機能を提供します

    1. ロックを取得するための非ブロッキングの試み(tryLock()
    2. 中断される可能性のあるロックを取得する試み(lockInterruptibly()
    3. タイムアウトする可能性のあるロックを取得する試み(tryLock(long, TimeUnit))。
  3. Lockクラスは、以下のような暗黙のモニターロックとはかなり異なる動作とセマンティクスを提供することもできます。

    1. 保証された注文
    2. 再入禁止の使用
    3. デッドロック検出

さまざまなタイプに関するこのSEの質問をご覧Locksください。

同期とロック

Synchroniedブロックの代わりに高度な同時実行APIを使用することで、スレッドセーフを実現できます。このドキュメントページは、スレッドセーフを実現するための優れたプログラミング構造を提供します。

ロックオブジェクトは、多くの同時アプリケーションを簡素化するロックイディオムをサポートします。

エグゼキュータは、スレッドを起動および管理するための高レベルAPIを定義します。java.util.concurrentによって提供されるエグゼキューター実装は、大規模なアプリケーションに適したスレッドプール管理を提供します。

並行コレクションを使用すると、データの大規模なコレクションを管理しやすくなり、同期の必要性を大幅に減らすことができます。

原子変数には、同期を最小限に抑え、メモリの一貫性エラーを回避するのに役立つ機能があります。

ThreadLocalRandom(JDK 7の場合)は、複数のスレッドから擬似乱数を効率的に生成します。

他のプログラミング構成については、java.util.concurrentおよびjava.util.concurrent.atomicパッケージも参照してください。


5

あなたがそれを決めたなら:

  • あなたがしなければならないことは、現在のオブジェクトをロックすることです。そして
  • メソッド全体よりも小さい粒度でロックしたい場合。

すると、synchronizezd(this)のタブが表示されません。

一部の人々は、メソッドのコンテンツ全体の中で(メソッドを同期するようにマークするのではなく)意図的にsynchronized(this)を使用します。人々が情報に基づいた選択をしている限り(たとえば、そうすることで実際にメソッドに追加のバイトコードが挿入され、これが潜在的な最適化にノックオン効果を及ぼす可能性があることを理解している限り)、特にこれに関する問題は見られません。プログラムの並行動作を常に文書化する必要があるので、「同期化された動作は動作を公開する」という議論がそれほど説得力があるとは思わない。

どのオブジェクトのロックを使用する必要があるかという問題については、現在のオブジェクトでの同期が、実行中のロジックとクラスの通常の使用方法によって予想される場合、問題はないと思います。たとえば、コレクションの場合、論理的にロックすると予想されるオブジェクトは通常、コレクション自体です。


1
「これがロジックによって期待されるなら...」も私が乗り越えようとしているポイントです。常にプライベートロックを使用することの意味はわかりませんが、害がなく、防御力が高いため、一般的なコンセンサスはそれがより良いということのようです。
eljenso、2009年

4

Brian Goetz著のJava Concurrency In Practiceと呼ばれる本に、これらのそれぞれがなぜ重要なテクニックであるかについての適切な説明があると思います。彼は、1つの点を非常に明確にします。オブジェクトの状態を保護するには、「EVERYWHERE」と同じロックを使用する必要があります。同期メソッドとオブジェクトでの同期は、多くの場合、密接に関連しています。たとえば、Vectorはそのすべてのメソッドを同期します。ベクターオブジェクトへのハンドルがあり、「存在しない場合に置く」を行う場合、ベクターが独自のメソッドを同期するだけでは、状態の破損から保護されません。同期(vectorHandle)を使用して同期する必要があります。これにより、ベクターへのハンドルを持つすべてのスレッドによってSAMEロックが取得され、ベクターの全体的な状態が保護されます。これはクライアント側ロックと呼ばれます。実際のところ、ベクターはすべてのメソッドを同期(this)/同期しているため、オブジェクトvectorHandleで同期すると、ベクターオブジェクトの状態が適切に同期されます。スレッドセーフコレクションを使用しているからといって、スレッドセーフであると信じるのはばかげています。これがまさに、ConcurrentHashMapがputIfAbsentメソッドを明示的に導入した理由です-そのような操作をアトミックにします。

要約すれば

  1. メソッドレベルで同期すると、クライアント側のロックが可能になります。
  2. プライベートロックオブジェクトがある場合-クライアント側のロックが不可能になります。これは、クラスに「存在しない場合の処理​​」タイプの機能がないことを知っている場合は問題ありません。
  3. ライブラリを設計している場合-これで同期するか、メソッドを同期するほうが賢明です。クラスがどのように使用されるかを決定する立場にあることはめったにないからです。
  4. Vectorがプライベートロックオブジェクトを使用していた場合、「存在しない場合に正しく」取得することは不可能でした。クライアントコードはプライベートロックへのハンドルを取得しないため、完全ロックを使用してその状態を保護するという基本的なルールに違反します。
  5. 他の人が指摘したように、このメソッドまたは同期されたメソッドでの同期には問題があります-誰かがロックを取得してロックを解放することはできません。他のすべてのスレッドは、ロックが解放されるのを待ち続けます。
  6. だからあなたが何をしているのかを知り、正しいものを採用してください。
  7. プライベートロックオブジェクトを使用すると、粒度が向上する(たとえば、2つの操作が無関係である場合)異なるロックによって保護され、スループットが向上すると誰かが主張しました。しかし、これはコードの臭いではなくデザインの臭いだと思います-2つの操作がまったく無関係である場合、なぜそれらは同じクラスの一部なのですか?クラスクラブが無関係な機能をまったく持つべきなのはなぜですか?ユーティリティクラスの可能性がありますか?うーむ-同じインスタンスを介して文字列操作とカレンダー日付フォーマットを提供するいくつかのユーティリティ?? ...少なくとも私には意味がありません!!

3

いいえ、常にそうする必要はありません。ただし、特定のオブジェクトに、スレッドセーフであることだけが必要な複数の懸念がある場合は、回避する傾向があります。たとえば、「ラベル」フィールドと「親」フィールドを持つ可変データオブジェクトがあるとします。これらはスレッドセーフである必要がありますが、一方を変更しても、他方の書き込み/読み取りがブロックされる必要はありません。(実際には、フィールドをvolatile宣言したり、java.util.concurrentのAtomicFooラッパーを使用したりして、これを回避します)。

同期は、スレッド同士がどのようにして相互に動作できるかを正確に考えるのではなく、大きなロックを押し下げるので、一般に少し扱いに​​くいです。synchronized(this)「私がロックを握っている間は誰このクラスの内容を変更することはできません」と言っているように、使用することはさらに不格好で反社会的です。実際にどれくらいの頻度でそれを行う必要がありますか?

私はむしろもっときめ細かいロックが欲しいです。すべての変更を停止したい場合でも(おそらくオブジェクトをシリアル化している場合)、すべてのロックを取得して同じことを実現できます。さらに、その方法がより明確になります。を使用する場合synchronized(this)、同期している理由や副作用の可能性については明確ではありません。を使用している場合synchronized(labelMonitor)、またはさらに優れてlabelLock.getWriteLock().lock()いる場合は、何をしているのか、およびクリティカルセクションの影響が制限されているのは明らかです。


3

短い答え:違いを理解し、コードに応じて選択する必要があります。

長い答え:一般に、競合を減らすために同期(これ)は避けたいと思いますが、プライベートロックは、注意しなければならない複雑さを追加します。したがって、適切なジョブに適切な同期を使用してください。マルチスレッドプログラミングの経験があまりない場合は、インスタンスのロックにこだわり、このトピックについて読んでください。(つまり、synchronize(this)を使用するだけでは、クラスが自動的に完全にスレッドセーフになるわけではありません。これは簡単なトピックではありませんが、慣れると、synchronize(this)を使用するかどうかの答えは自然にわかります。。


それはあなたの経験に依存すると言うとき、私はあなたを正しく理解していますか?
eljenso、2009年

そもそも、作成するコードによって異なります。同期(これ)を使用しないように変更する場合は、もう少し経験が必要になる可能性があると言っています。
tcurdt 2009年

2

ロックは、可視性のため、または競合につながる可能性がある同時変更から一部のデータを保護するために使用されます。

プリミティブ型の操作をアトミックにする必要がある場合は、などの使用可能なオプションがありますAtomicInteger

しかし、あなたは次のように相互に関連している2つの整数があるとxし、y相互に関連していると、原子的に変更する必要があります座標を、。次に、同じロックを使用してそれらを保護します。

ロックは、互いに関連する状態のみを保護する必要があります。それ以上でもそれ以下でもありません。synchronized(this)各メソッドで使用する場合、クラスの状態が無関係でも、無関係な状態を更新してもすべてのスレッドが競合に直面します。

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

上記の例では私は一つの方法を持っている両方の変異しx且つyない二つの異なる方法などとxy関連していると私は突然変異のための2つの異なる方法を与えていた場合xy別々に、それがされていないスレッドセーフ。

この例は、単に示すためのものであり、必ずしも実装方法を示しているわけではありません。それを行う最善の方法は、それを不変にすることです

Point例とは反対に、TwoCounters@ Andreasによってすでに提供されている例があります。ここでは、状態は互いに無関係であるため、2つの異なるロックによって保護されています。

異なるロックを使用して無関係な状態を保護するプロセスは、ロックストライピングまたはロックスプリッティングと呼ばれます。


1

これで同期しない理由は、複数のロックが必要になる場合があるためです(2番目のロックは、いくつかの追加の考えの後で削除されることがよくありますが、それでも中間状態で必要です)。あなたは上でロックした場合は、この、あなたは常にある2つのロックの一つ覚えておく必要があり、この ; プライベートオブジェクトをロックすると、変数名からそれがわかります。

読者の視点から見ると、これにロックがかかっている場合、常に次の2つの質問に答える必要があります。

  1. これによってどのようなアクセスが保護されますか?
  2. 1つのロックで十分ですが、誰かがバグを導入していませんか?

例:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

2つのスレッドがのlongOperation()2つの異なるインスタンスで始まる場合BadObject、それらはロックを取得します。を呼び出すときl.onMyEvent(...)は、どちらのスレッドも他のオブジェクトのロックを取得できないため、デッドロックが発生します。

この例では、短い操作用と長い操作用の2つのロックを使用して、デッドロックを解消できます。


2
この例でデッドロックを取得する唯一の方法は、BadObjectAがlongOperationBを呼び出してAを渡しmyListener、その逆の場合です。不可能ではありませんが、かなり複雑で、以前のポイントをサポートしています。
eljenso 2013

1

すでにここで述べたように、同期されたブロックがユーザー定義変数をロックオブジェクトとして使用できるのは、同期された関数が「this」のみを使用する場合です。そしてもちろん、同期する必要がある関数の領域などを操作できます。

しかし、同期された関数と、「this」をロックオブジェクトとして使用して関数全体をカバーするブロックとの間に違いはないと誰もが言う。それは真実ではありません、違いは両方の状況で生成されるバイトコードにあります。同期ブロックの使用の場合は、「this」への参照を保持するローカル変数を割り当てる必要があります。その結果、関数のサイズが少し大きくなります(関数の数が少ない場合は関係ありません)。

ここで見つけることができる違いのより詳細な説明:http : //www.artima.com/insidejvm/ed2/threadsynchP.html

また、次の観点から、同期ブロックの使用は適切ではありません。

synchronizedキーワードは、1つの領域で非常に制限されています。同期ブロックを終了するとき、そのロックを待機しているすべてのスレッドのブロックを解除する必要がありますが、ロックを取得できるのはこれらのスレッドの1つだけです。他のすべてのユーザーは、ロックが取得されたことを確認し、ブロックされた状態に戻ります。これは、多くの無駄な処理サイクルではありません。スレッドのブロックを解除するためのコンテキスト切り替えには、ディスクからメモリをページングすることも含まれます。これは非常に非常にコストがかかります。

この領域の詳細については、この記事を読むことをお勧めします。http//java.dzone.com/articles/synchronized-considered


1

これは実際には他の回答を補足するだけですが、ロックにプライベートオブジェクトを使用することに対する主な異議が、ビジネスロジックに関連しないフィールドでクラスが乱雑になる場合は、Project Lombokは@Synchronizedコンパイル時にボイラープレートを生成する必要があります。

@Synchronized
public int foo() {
    return 0;
}

コンパイルする

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}

0

同期(これ)を使用する良い例です。

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

ここでわかるように、ここで同期を使用して、長い(実行メソッドの無限ループの可能性があります)と同期されたいくつかのメソッドを簡単に連携させます。

もちろん、同期化されたプライベートフィールドを使用すると、非常に簡単に書き換えることができます。しかし、同期化されたメソッドを使用したデザインが既にある場合(つまり、レガシークラスから派生した場合、同期化(これ)が唯一のソリューションになることがあります)。


ここでは、任意のオブジェクトをロックとして使用できます。である必要はありませんthis。それはプライベートフィールドである可能性があります。
finnw 2010

正解ですが、この例の目的は、メソッド同期を使用することを決定した場合に、適切に同期する方法を示すことでした。
Bart Prokop

0

それはあなたがしたい仕事に依存しますが、私はそれを使いません。また、最初にsynchronize(this)で達成したいスレッドの節約ができなかったかどうかを確認しますか?APIには便利なロックもいくつかあります:)


0

依存関係のないコードのアトミックな部分での一意のプライベート参照の可能な解決策に言及したいだけです。ロック付きの静的Hashmapと、スタック情報(完全なクラス名と行番号)を使用して必要な参照を自動的に作成するatomic()という名前の単純な静的メソッドを使用できます。その後、新しいロックオブジェクトを作成せずに、このメソッドをステートメントの同期で使用できます。

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

0

synchronized(this)ロックメカニズムとしての使用は避けてください。これにより、クラスインスタンス全体がロックされ、デッドロックが発生する可能性があります。このような場合、コードをリファクタリングして特定のメソッドまたは変数のみをロックします。これにより、クラス全体がロックされなくなります。Synchronisedメソッドレベル内で使用できます。以下のコードは、
を使用する代わりにsynchronized(this)、メソッドをロックする方法を示しています。

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

0

この質問はすでに解決されているかもしれませんが、2019年の私の2セント。

'this'のロックは、何をしているのかわかっていれば悪くありませんが、 'this'のロックは裏で行われます(残念ながら、これはメソッド定義の同期キーワードで許可されています)。

クラスのユーザーがロックを「盗む」ことができるようにしたい場合(つまり、他のスレッドがロックを処理できないようにする場合)、実際には、別の同期メソッドが実行されている間、同期されたすべてのメソッドを待機させる必要があります。それは意図的でよく考えられたものでなければなりません(そのため、ユーザーが理解できるように文書化されています)。

さらに詳しく説明すると、逆に、アクセスできないロックをロックした場合(だれもロックを「盗む」ことができない場合、完全に制御できるなど)、何を「獲得」(または「失う」)しているのかを知る必要があります。 ..)。

私にとっての問題は、メソッド定義シグネチャの同期されたキーワードにより、プログラマーが何をロックするかについて考えることがあまりにも簡単になり、マルチで問題に遭遇したくない場合に考えることは非常に重要なことです-スレッド化されたプログラム。

「通常」クラスのユーザーにこれらのことを実行してほしくない、または「通常」あなたが望んでいると主張することはできません...コーディングする機能によって異なります。すべてのユースケースを予測することはできないため、経験則を作ることはできません。

たとえば、内部ロックを使用するプリントライターを検討しますが、出力をインターリーブしたくない場合、複数のスレッドからそれを使用するのに苦労します。

クラスの外でロックにアクセスできるかどうかは、クラスの機能に基づいてプログラマーが決定します。これはAPIの一部です。たとえば、同期化(this)から同期化(provateObjet)に移動して、コードを使用して変更を壊す危険を冒すことはできません。

注1:明示的なロックオブジェクトを使用してそれを公開することで、同期(this)の「達成」を達成できることはわかっていますが、動作が十分に文書化されており、「this」のロックの意味が実際にわかっている場合は不要だと思います。

注2:あるコードが誤ってロックを盗んでいる場合、そのバグをバグとして解決する必要があるという主張には同意しません。これはある意味では、メソッドがパブリックである必要がない場合でも、すべてのメソッドをパブリックにできると言っているのと同じ議論です。誰かが「誤って」私が意図したプライベートメソッドであると呼んでいる場合、それはバグです。そもそもなぜこの事故を有効にするのか!ロックを盗む能力がクラスの問題である場合は、許可しないでください。それと同じくらい簡単です。


-3

ポイント1(他の誰かがロックを使用している)と2(すべてのメソッドが同じロックを不必要に使用している)は、かなり大きなアプリケーションで発生する可能性があると思います。特に、開発者間に適切なコミュニケーションがない場合。

それは石にキャストされません、それは主に良い習慣とエラーを防ぐ問題です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.