同期とロック


182

java.util.concurrentAPIはと呼ばれるクラスを提供しますLock。これは基本的に、重要なリソースにアクセスするためにコントロールをシリアル化します。park()およびなどのメソッドを提供しunpark()ます。

synchronizedキーワードとusing wait()およびmethodを使用できれば、同様のことができますnotify() notifyAll()

これらのうちどれが実際に優れているのか、なぜですか?


回答:


178

オブジェクトを単にロックしているのであれば、私は synchronized

例:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

try{} finally{}どこでも明示的に行う必要があります。

同期化されている場合、それは非常に明確であり、誤解することは不可能です。

synchronized(myObject) {
    doSomethingNifty();
}

そうは言っても、Locksは、このようにクリーンな方法で取得および解放できない複雑なものには、より便利な場合があります。正直に言えばLock、最初は裸のsの使用を避け、必要に応じてa CyclicBarrierやa などのより高度な同時実行制御を使用することをお勧めしLinkedBlockingQueueます。

私が使用する理由だったことがありませんwait()か、notify()しかし、いくつかの良いものがあるかもしれませんが。


1
LockSupportの待機/通知とパーク/アンパークの違いは何ですか?docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/...
Pacerier

6
最初の例はロックで理にかなっていますが、try finallyブロックを使用すると、ロックが解放されないことでその問題が回避されることに気付きました
William Reed

Ahh ... C ++でRAIIモデルを評価する瞬間の1つ。std::lock_guard
WhiZTiM

67

これらのうちどれが実際に優れているのか、なぜですか?

私がいることを発見したLockCondition(および他の新しいconcurrentクラスが)ツールボックスのためにちょうどより多くのツールがあります。私は古いクローハンマー(synchronizedキーワード)で必要なすべてのことを行うことができましたが、状況によっては使いづらかったです。ツールボックスにツールをさらに追加すると、これらの厄介な状況のいくつかがはるかに簡単になりました。ラバーマレット、ボールピーンハンマー、プライバー、およびいくつかのネイルパンチです。しかし、私の古いクローハンマーはまだその使用のシェアを見ています。

私はどちらか一方が他方より本当に「良い」とは思わないが、どちらもさまざまな問題により適している。簡単に言うと、の単純なモデルとスコープ指向の性質はsynchronized、コード内のバグから私を保護するのに役立ちますが、これらの同じ利点は、より複雑なシナリオでは妨げになることがあります。対処するためにコンカレントパッケージが作成された、これらのより複雑なシナリオ。ただし、このより高いレベルの構成を使用するには、コードでより明示的かつ注意深い管理が必要です。

===

私は思うのJavaDocは間の区別記述の良い仕事しLockやがsynchronized(強調は私です):

ロックの実装は、同期されたメソッドとステートメントを使用して取得できるよりも広範なロック操作を提供します。それらはより柔軟な構造化を可能にし、まったく異なるプロパティを持ち、複数の関連するConditionオブジェクトをサポートするかもしれません。

...

使用synchronizedメソッドや文は、すべてのオブジェクトに関連付けられた暗黙の監視ロックへのアクセスを提供しますが、すべてのロック取得を強制し、ブロック構造の形で発生するリリース:とき、複数のロックがされている取得し、それらは逆の順序で解放されなければならない、とすべてのロックは、それらが取得されたのと同じ字句スコープで解放する必要があります

同期されたメソッドとステートメントのスコープメカニズムにより、モニターロックを使用したプログラミングがはるかに容易になり、ロックに関連する多くの一般的なプログラミングエラーを回避できますが、より柔軟な方法でロックを操作する必要がある場合があります。たとえば、*いくつかのアルゴリズム*は、同時にアクセスされるデータ構造をトラバースするために、「ハンドオーバーハンド」または「チェーンロック」を使用する必要があります。次に、Bを解放し、Dを取得します。Lockインターフェースの実装は、異なるスコープロックを取得および解放できるようにすることで、このような手法の使用を可能にします。複数のロックを任意の順序で取得および解放できるようにします。

この柔軟性の向上により、追加の責任が生じますブロック構造ロックが存在しないことは、ロックの自動解除除去同期方法及びステートメントで起こります。ほとんどの場合、次のイディオムを使用する必要があります。

...

ときにロックとロック解除が異なるスコープで発生し、世話をするために取られなければならないことを確認ロックが保持されている間に実行されるすべてのコードがあることのtry-最終的またはのtry-catchにより保護されているためにロックが解除されていることを確認し、必要なとき。

ロック実装が提供する追加機能を提供することによって同期方法および文の使用上を取得する非ブロック試行ロック(のtryLock())しようとする試みを遮断することができるロック獲得 lockInterruptiblyを(()、との試み取得タイムアウトする可能性のあるロック(tryLock(long、TimeUnit))。

...


23

あなたは内のユーティリティすべて達成することができますjava.util.concurrentのは、 のような低レベルのプリミティブで行うsynchronizedvolatileまたは待機 / 通知を

ただし、並行性はトリッキーであり、ほとんどの人は少なくともその一部を誤解して、コードを不正確または非効率的(あるいはその両方)にします。

並行APIは、より簡単な(そして安全な)使用方法である、より高レベルのアプローチを提供します。簡単に言えば、synchronized, volatile, wait, notifyもう直接使用する必要はありません。

ロック クラス自体は、直接的に(あなたが使用できることを、あなたも使用する必要がない場合も、このツールボックスの低レベル側にあるQueuesセマフォやものなど、ほとんどの時間)。


2
単純な古いwait / notifyは、java.util.concurrent.locks.LockSupportのpark / unparkよりも低いレベルのプリミティブと見なされますか、それともその逆ですか?
パセリエ

@Pacerier:私は両方とも低レベル(つまり、アプリケーションプログラマーが直接使用することを避けたいもの)だと考えていますが、java.util.concurrencyの低レベルの部分(ロックパッケージなど)は確かに上に構築されていますネイティブJVMプリミティブの待機/通知(さらに低いレベル)。
Thilo

2
いいえ、私は3:Thread.sleep / interrupt、Object.wait / notify、LockSupport.park / unparkのうち、最も原始的なものを教えてください。
Pacerier 2012年

2
@Thilo java.util.concurrent言語機能(synchronizedなど)よりも[一般的に]簡単なステートメントをどのようにサポートするかわかりません。を使用するjava.util.concurrentときは、lock.lock(); try { ... } finally { lock.unlock() }コードを書く前に完了する習慣をつける必要がありますが、a を使用すると、synchronized基本的に最初から問題ありません。これだけを基にしても、私はsynchronized(その動作が必要であれば)よりも簡単だと思いますjava.util.concurrent.locks.Lockパー4
Iwan Aucamp 2014年

1
java.util.concurrentの前に利用できないネイティブのCAS呼び出しに依存しているため、同時実行プリミティブだけでAtomicXXXクラスの動作を正確に複製できるとは思わないでください。
Duncan Armstrong

15

synchronizedまたはを使用する主な理由は4つありますjava.util.concurrent.Lock

注:組み込みロックとは、同期ロックのことです。

  1. Java 5がReentrantLocksを発表したとき、それらは本質的なロックとはかなり顕著なスループットの違いを持っ​​ていることが証明されました。より高速なロックメカニズムを探していて、1.5を実行している場合は、jucReentrantLockを検討してください。Java 6の固有のロックが比較できるようになりました。

  2. jucLockには、ロックのためのさまざまなメカニズムがあります。割り込み可能ロック-ロックしているスレッドが割り込まれるまでロックを試みます。時限ロック-一定時間ロックを試み、成功しない場合はあきらめます。tryLock-他のスレッドがロックを保持している場合は、ロックを試み、中止します。このすべては、単純なロックを除いて含まれています。組み込みロックは単純なロックのみを提供します

  3. スタイル。1と2の両方が、私を含め、ほとんどの人が関係しているもののカテゴリに該当しない場合、組み込みロックセマンティクスは読みやすく、jucLockロックより冗長ではありません。
  4. 複数の条件。ロックしたオブジェクトは、通知され、単一のケースを待機することができます。LockのnewConditionメソッドを使用すると、1つのLockに複数の理由で待機または通知することができます。実際にこの機能を実際に必要とすることはまだありませんが、それを必要とする人にとっては素晴らしい機能です。

コメントの詳細が気に入りました。さらにもう1つ箇条書きを追加します。ReadWriteLockは、いくつかのスレッドを処理している場合に便利な動作を提供します。そのうちのいくつかだけがオブジェクトに書き込む必要があります。複数のスレッドが同時にオブジェクトを読み取ることができ、別のスレッドがすでにオブジェクトに書き込んでいる場合にのみブロックされます。
Sam Goldberg

5

バートFの回答にさらにいくつか追加したいと思い ます。

Locks暗黙的なモニター(synchronizedロック)よりも表現力のある、より細かいロック制御のためのさまざまな方法をサポートします。

ロックは、共有リソースへの排他的アクセスを提供します。一度に1つのスレッドのみがロックを取得でき、共有リソースへのすべてのアクセスには、最初にロックを取得する必要があります。ただし、ReadWriteLockの読み取りロックなど、一部のロックは共有リソースへの同時アクセスを許可する場合があります。

ドキュメントページからのロックオーバー同期の利点

  1. 同期されたメソッドまたはステートメントを使用すると、すべてのオブジェクトに関連付けられている暗黙のモニターロックにアクセスできますが、すべてのロックの取得と解放はブロック構造で発生します。

  2. ロックの実装はlock (tryLock())、を取得するための非ブロッキングの試み、中断される可能性のあるロックの取得の試み(lockInterruptibly()および可能性のあるロックの取得の試み)を提供することにより、同期メソッドおよびステートメントの使用に追加の機能を提供しますtimeout (tryLock(long, TimeUnit))

  3. Lockクラスは、保証された順序付け、再入不可の使用法、デッドロック検出など、暗黙のモニターロックとはまったく異なる動作とセマンティクスを提供することもできます。

ReentrantLock:私の理解に従って簡単に言うとReentrantLock、オブジェクトは1つのクリティカルセクションから別のクリティカルセクションに再入することができます。1つのクリティカルセクションに入るロックが既にあるため、現在のロックを使用して、同じオブジェクトの他のクリティカルセクションを作成できます。

ReentrantLockこの記事の主な機能

  1. 割り込みロック機能。
  2. ロックの待機中にタイムアウトする機能。
  3. 公正なロックを作成する力。
  4. ロックを待機しているスレッドのリストを取得するAPI。
  5. ブロックせずにロックを試す柔軟性。

を使用ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLockして、読み取りおよび書き込み操作の詳細なロックをさらに制御できます 。

これら3つのReentrantLocksとは別に、java 8はもう1つのロックを提供します

StampedLock:

Java 8には、StampedLockと呼ばれる新しい種類のロックが付属しています。これは、上記の例のように、読み取りおよび書き込みロックもサポートします。ReadWriteLockとは対照的に、StampedLockのロックメソッドは、long値で表されるスタンプを返します。

これらのスタンプを使用して、ロックを解除するか、ロックがまだ有効かどうかを確認できます。さらに、スタンプ付きロックは、楽観的ロックと呼ばれる別のロックモードをサポートします。

さまざまなタイプのとロックの使用に関するこの記事ReentrantLockStampedLockご覧ください。


4

主な違いは、公平性です。つまり、リクエストはFIFOで処理されますか、それとも割り込みが発生しますか メソッドレベルの同期により、ロックの公平またはFIFO割り当てが保証されます。使用する

synchronized(foo) {
}

または

lock.acquire(); .....lock.release();

公平性は保証されません。

ロックに対して多くの競合がある場合、新しい要求がロックを取得し、古い要求がスタックする割り込みが簡単に発生する可能性があります。ロックのために200個のスレッドが短い順序で到着し、2番目に到着したスレッドが最後に処理された場合を見てきました。これは一部のアプリケーションでは問題ありませんが、他のアプリケーションでは致命的です。

このトピックの詳細については、Brian Goetzの「Java Concurrency In Practice」の本のセクション13.3を参照してください。


5
「メソッドレベルの同期により、ロックの公平またはFIFO割り当てが保証されます。」=>本当に?同期されたメソッドの動作は、メソッドのコンテンツを同期された{}ブロックにラップする場合とは異なります。私はそうは思わないでしょう、あるいは私はその文章が間違っていると理解しました...?
weiresr 2012年

はい、驚くべきことですが、直観に反することは正しいです。ゲッツの本が一番の説明です。
Brian Tarbox、

@BrianTarboxによって提供されるコードを見ると、同期ブロックは「this」以外のオブジェクトを使用してロックしています。理論的には、ブロックがロックとして「this」を使用している限り、同期されたメソッドと上記のメソッドの本体全体を同期されたブロック内に置くことの間に違いはありません。
xburgos

回答は引用を含むように編集する必要があり、ここでの「保証」は確定的ではなく「統計的保証」であることを明確にしてください。
Nathan Hughes

申し訳ありませんが、数日前に誤ってこの回答に反対票を投じたことがわかりました(不器用なクリック)。残念ながら、SOでは現在、元に戻すことはできません。後で修正できるといいのですが。
ミッコエストルンド2018年

3

Brian Goetzの「Java Concurrency In Practice」の本のセクション13.3:「...デフォルトのReentrantLockのように、組み込みロックは確定的な公平性の保証を提供しませんが、ほとんどのロック実装の統計的公平性の保証はほとんどすべての状況で十分です...」


2

ロックは、プログラマーの生活を楽にします。ロックで簡単に実行できるいくつかの状況を次に示します。

  1. 1つの方法でロックし、別の方法でロックを解除します。
  2. ただし、2つのスレッドが2つの異なるコードで作業している場合、最初のスレッドでは、2番目のスレッドの特定のコードが前提条件となっています(他のいくつかのスレッドも2番目の同じコードで作業している場合)。同時にスレッド)。共有ロックは、この問題を非常に簡単に解決できます。
  3. モニターの実装。たとえば、putおよびgetメソッドが他の多くのスレッドから実行される単純なキュー。ただし、複数のput(またはget)メソッドを同時に実行したり、putおよびgetメソッドを同時に実行したりすることは望ましくありません。プライベートロックを使用すると、これを簡単に実現できます。

一方、ロックと条件は同期メカニズム上に構築されます。したがって、ロックを使用して実現できるのと同じ機能を確実に実現できます。ただし、同期を使用して複雑なシナリオを解決すると、人生が困難になり、実際の問題の解決から逸脱する可能性があります。


1

ロックと同期の主な違い:

  • ロックを使用すると、任意の順序でロックを解放および取得できます。
  • 同期では、取得した順序でのみロックを解放できます。

0

ロックと同期ブロックはどちらも同じ目的を果たしますが、使用方法によって異なります。以下の部分を検討してください

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

上記の場合、スレッドが同期ブロックに入ると、他のブロックもロックされます。同じオブジェクト上にそのような同期ブロックが複数ある場合、すべてのブロックがロックされます。このような状況では、java.util.concurrent.Lockを使用して、ブロックの不要なロックを防止できます。

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