394

synchronized(this)Java同期についてSOに質問が出たときはいつでも、避けるべきだと非常に熱心に指摘する人もいます。代わりに、彼らは、プライベート参照のロックが優先されると主張しています。

与えられた理由のいくつかは次のとおりです。

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

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

したがって、常に回避synchronized(this)して、プライベート参照のロックに置き換える必要がありますか?


いくつかのさらなる情報(答えが与えられると更新されます):

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

23 に答える 23

133

各ポイントを個別に説明します。

  1. いくつかの邪悪なコードがロックを盗む可能性があります (これは非常に人気があり、「偶発的な」変種もあります)

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

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

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

  3. あなたは(不必要に)あまりにも多くの情報を公開しています

    これは #1 の変形です。の使用はsynchronized(this)インターフェイスの一部です。これを公開したくない/公開する必要がない場合は、公開しないでください。

于 2009-01-14T11:05:41.177 に答える
89

さて、最初にそれが指摘されるべきです:

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

意味的には次のものと同等です。

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

これは、を使用しない理由の1つsynchronized(this)です。synchronized(this)あなたはあなたがブロックの周りで何かをすることができると主張するかもしれません。通常の理由は、同期チェックを実行する必要がまったくないようにすることです。これにより、あらゆる種類の同時実行の問題、特に、比較的単純なチェックを行うことがいかに難しいかを示すダブルチェックロックの問題が発生します。スレッドセーフ。

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

また、あなたがほのめかしたように、プライベートロックは粒度を制御することができます。オブジェクトに対する一連の操作は、別の操作とはまったく関係がない場合がありますがsynchronized(this)、それらすべてへのアクセスは相互に除外されます。

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

于 2009-01-14T10:42:57.277 に答える
58

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

次のコードがあるとします。

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


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

変数aを変更する方法 1と変数bを変更する方法 2では、2 つのスレッドによる同じ変数の同時変更を避ける必要があります。ただし、スレッド1がaを変更スレッド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 ++;      
        }
        // ................
    }

}

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

于 2009-01-14T11:23:27.273 に答える
7
  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()
     }
   }
 }

Synchronized(this) に対する Lock の利点

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

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

    1. ロックを取得するためのノンブロッキング試行 ( tryLock())
    2. 中断可能なロックを獲得しようとする試み ( lockInterruptibly())
    3. タイムアウトできるロックを取得しようとしています ( tryLock(long, TimeUnit))。
  3. Lock クラスは、次のような暗黙のモニター ロックとはまったく異なる動作とセマンティクスを提供することもできます。

    1. 注文保証
    2. 再入不可の使用法
    3. デッドロック検出

さまざまなタイプのこの SE の質問を見てくださいLocks

同期とロック

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

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

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

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

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

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

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

于 2016-04-18T11:05:24.450 に答える
7

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

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

于 2009-01-14T14:34:47.823 に答える
5

あなたがそれを決定した場合:

  • 必要なことは、現在のオブジェクトをロックすることです。と
  • メソッド全体よりも小さい粒度でロックしたい。

次に、synchronizezd(this) に対するタブーが表示されません。

一部の人々は、実際にどのオブジェクトが同期されているかが「読者にとってより明確」であると考えるため、メソッドの内容全体の中で(メソッドを同期とマークする代わりに) synchronized(this) を意図的に使用します。人々が十分な情報に基づいた選択をしている限り (たとえば、そうすることで実際に余分なバイトコードをメソッドに挿入していて、これが潜在的な最適化に波及効果を及ぼす可能性があることを理解している)、私はこれに特に問題はないと思います。 . プログラムの並行動作を常に文書化する必要があるため、「「同期」が動作を公開する」という議論がそれほど説得力があるとは思いません。

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

于 2009-01-14T14:59:48.193 に答える
3

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

同期は、スレッドが相互に回避する方法を正確に考えるのではなく、大きなロックダウンを叩くため、一般的に少し不器用です。「私が鍵を握っている間、誰もこのクラスでも変更できない」と言っているので、使用synchronized(this)はさらに不器用で反社会的です。実際にそれを行う必要がある頻度はどれくらいですか?

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

于 2009-01-14T10:50:44.640 に答える
3

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

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

于 2009-01-14T11:04:53.893 に答える
2

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

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

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

ロックは、互いに関連する状態のみを保護する必要があります。それ以下でもそれ以上でもありません。各メソッドで使用する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;
   }
}

上記の例では、関連する2 つの異なるメソッドではなく、両方を変更するメソッドが 1 つしかありません。変更xするための 2 つの異なるメソッドを個別に指定した場合、それはスレッドセーフではありませんでした。yxyxy

この例は単に説明するためのものであり、必ずしも実装方法を示すものではありません。それを行う最善の方法は、IMMUTABLEにすることです。

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

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

于 2013-11-20T07:43:42.890 に答える
1

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

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

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

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

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

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

于 2013-07-16T08:50:32.087 に答える
1

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

読者の観点からは、thisがロックされている場合は、常に次の 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(...);
        ...
    }
}

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

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

于 2013-02-20T06:26:52.887 に答える
0

synchronized(this) を使用する良い例です。

// 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...
}

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

もちろん、同期されたプライベート フィールドを使用すると、非常に簡単に書き換えることができます。しかし、同期メソッド (つまり、派生元のレガシー クラス) を使用した設計が既にある場合は、synchronized(this) が唯一の解決策になる場合があります。

于 2010-07-13T12:38:54.900 に答える
0

やりたいタスクにもよりますが、私は使いません。また、達成したいスレッドセーブネスが最初に synchronize(this) によって実行できなかったかどうかを確認してください。APIには、役立つかもしれない素敵なロックもいくつかあります:)

于 2009-01-14T11:16:20.093 に答える
-3

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

それは石に投げ込まれていません、それは主に良い習慣とエラーを防ぐことの問題です。

于 2009-01-14T10:43:12.517 に答える