例を挙げて、同期ブロックに対する同期メソッドの利点を教えてもらえますか?
23 に答える
例を挙げて、同期ブロックに対する同期メソッドの利点を誰か教えてもらえますか? ありがとう。
ブロックに対して同期メソッドを使用する明確な利点はありません。
おそらく唯一の (ただし、利点とは言いませんが) オブジェクト参照を含める必要がないことですthis
。
方法:
public synchronized void method() { // blocks "this" from here....
...
...
...
} // to here
ブロック:
public void method() {
synchronized( this ) { // blocks "this" from here ....
....
....
....
} // to here...
}
見る?まったくメリットがありません。
ただし、ブロックにはメソッドよりも利点があります。メソッドを同期するとオブジェクト全体がロックされるのに対し、別のオブジェクトをロックとして使用できるため、主に柔軟性があります。
比較:
// locks the whole object
...
private synchronized void someInputRelatedWork() {
...
}
private synchronized void someOutputRelatedWork() {
...
}
対。
// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();
private void someInputRelatedWork() {
synchronized(inputLock) {
...
}
}
private void someOutputRelatedWork() {
synchronized(outputLock) {
...
}
}
また、メソッドが大きくなった場合でも、同期セクションを分離したままにすることができます。
private void method() {
... code here
... code here
... code here
synchronized( lock ) {
... very few lines of code here
}
... code here
... code here
... code here
... code here
}
唯一の実際の違いは、同期ブロックが同期するオブジェクトを選択できることです。同期メソッドは'this'
(または同期クラス メソッドの対応する Class インスタンス) のみを使用できます。たとえば、これらは意味的に同等です。
synchronized void foo() {
...
}
void foo() {
synchronized (this) {
...
}
}
後者は、多くの場合メンバー変数など、任意のオブジェクトの関連付けられたロックを競合できるため、より柔軟です。また、ブロックの前後で同時実行コードをメソッド内で実行できるため、よりきめ細かくなります。もちろん、並行コードを個別の非同期メソッドにリファクタリングすることで、同期メソッドを簡単に使用することもできます。コードが理解しやすい方を使用してください。
同期方式
長所:
- IDE は、同期されたメソッドを示すことができます。
- 構文はよりコンパクトです。
- 同期されたブロックを別々のメソッドに強制的に分割します。
短所:
- これに同期するため、部外者もそれに同期できるようになります。
- コードを同期ブロックの外に移動するのはより困難です。
同期ブロック
長所:
- ロックにプライベート変数を使用できるようにするため、ロックを強制的にクラス内に留めることができます。
- 同期ブロックは、変数への参照を検索することで見つけることができます。
短所:
- 構文がより複雑になるため、コードが読みにくくなります。
個人的には、同期が必要なものだけに焦点を当てたクラスで同期メソッドを使用することを好みます。このようなクラスはできるだけ小さくする必要があるため、同期を簡単に確認できる必要があります。他の人は同期を気にする必要はありません。
主な違いは、同期ブロックを使用すると、これ以外のオブジェクトをロックできるため、より柔軟になることです。
メッセージ キューと、複数のメッセージ プロデューサーおよびメッセージ コンシューマーがあるとします。プロデューサーが互いに干渉することは望ましくありませんが、コンシューマーはプロデューサーを待たずにメッセージを取得できる必要があります。オブジェクトを作成するだけです
Object writeLock = new Object();
そしてこれからは、プロデューサーが新しいメッセージを追加したいときはいつでも、それをロックするだけです:
synchronized(writeLock){
// do something
}
そのため、コンシューマーは引き続き読み取ることができ、プロデューサーはロックされます。
同期方式
同期メソッドには 2 つの効果があります。
まず、1 つのスレッドがオブジェクトの同期メソッドを実行している場合、同じオブジェクトの同期メソッドを呼び出す他のすべてのスレッドは、最初のスレッドがオブジェクトの処理を完了するまでブロック (実行を中断) します。
次に、同期メソッドが終了すると、同じオブジェクトに対する同期メソッドの後続の呼び出しとの先行発生関係が自動的に確立されます。これにより、オブジェクトの状態の変更がすべてのスレッドに表示されることが保証されます。
コンストラクターは同期できないことに注意してください。コンストラクターで synchronized キーワードを使用すると、構文エラーになります。コンストラクターの同期は意味がありません。これは、オブジェクトを作成するスレッドのみが、構築中にオブジェクトにアクセスできる必要があるためです。
同期ステートメント
同期されたメソッドとは異なり、同期されたステートメントは、固有のロックを提供するオブジェクトを指定する必要があります。ほとんどの場合、これを使用してリストまたはマップへのアクセスを同期しますが、オブジェクトのすべてのメソッドへのアクセスをブロックしたくありません。
Q: 固有ロックと同期 同期は、固有ロックまたはモニター ロックと呼ばれる内部エンティティを中心に構築されています。(API 仕様では、このエンティティを単に「モニター」と呼ぶことがよくあります。) 組み込みロックは、同期の両方の側面で役割を果たします。つまり、オブジェクトの状態への排他的アクセスを強制し、可視性に不可欠な事前発生関係を確立します。
すべてのオブジェクトには固有のロックが関連付けられています。慣例により、オブジェクトのフィールドへの排他的かつ一貫したアクセスを必要とするスレッドは、オブジェクトにアクセスする前にオブジェクトの固有ロックを取得し、それらの操作が完了したら固有ロックを解放する必要があります。スレッドは、ロックを取得してからロックを解放するまでの間、固有のロックを所有していると見なされます。スレッドが固有ロックを所有している限り、他のスレッドが同じロックを取得することはできません。他のスレッドは、ロックを取得しようとするとブロックされます。
package test;
public class SynchTest implements Runnable {
private int c = 0;
public static void main(String[] args) {
new SynchTest().test();
}
public void test() {
// Create the object with the run() method
Runnable runnable = new SynchTest();
Runnable runnable2 = new SynchTest();
// Create the thread supplying it with the runnable object
Thread thread = new Thread(runnable,"thread-1");
Thread thread2 = new Thread(runnable,"thread-2");
// Here the key point is passing same object, if you pass runnable2 for thread2,
// then its not applicable for synchronization test and that wont give expected
// output Synchronization method means "it is not possible for two invocations
// of synchronized methods on the same object to interleave"
// Start the thread
thread.start();
thread2.start();
}
public synchronized void increment() {
System.out.println("Begin thread " + Thread.currentThread().getName());
System.out.println(this.hashCode() + "Value of C = " + c);
// If we uncomment this for synchronized block, then the result would be different
// synchronized(this) {
for (int i = 0; i < 9999999; i++) {
c += i;
}
// }
System.out.println("End thread " + Thread.currentThread().getName());
}
// public synchronized void decrement() {
// System.out.println("Decrement " + Thread.currentThread().getName());
// }
public int value() {
return c;
}
@Override
public void run() {
this.increment();
}
}
同期メソッド、ブロック、および同期なしのさまざまな出力をクロス チェックします。
注:静的同期メソッドとブロックは Class オブジェクトで機能します。
public class MyClass {
// locks MyClass.class
public static synchronized void foo() {
// do something
}
// similar
public static void foo() {
synchronized(MyClass.class) {
// do something
}
}
}
Java コンパイラがソース コードをバイト コードに変換するとき、同期メソッドと同期ブロックの処理は大きく異なります。
JVM が同期メソッドを実行すると、実行中のスレッドは、メソッドの method_info 構造に ACC_SYNCHRONIZED フラグが設定されていることを識別し、オブジェクトのロックを自動的に取得してメソッドを呼び出し、ロックを解放します。例外が発生した場合、スレッドは自動的にロックを解放します。
一方、メソッド ブロックを同期すると、オブジェクトのロックと例外処理を取得するための JVM の組み込みサポートがバイパスされ、機能をバイト コードで明示的に記述する必要があります。同期ブロックを含むメソッドのバイト コードを読み取ると、この機能を管理するための 10 を超える追加操作が表示されます。
これは、同期メソッドと同期ブロックの両方を生成するための呼び出しを示しています。
public class SynchronizationExample {
private int i;
public synchronized int synchronizedMethodGet() {
return i;
}
public int synchronizedBlockGet() {
synchronized( this ) {
return i;
}
}
}
このsynchronizedMethodGet()
メソッドは、次のバイト コードを生成します。
0: aload_0
1: getfield
2: nop
3: iconst_m1
4: ireturn
synchronizedBlockGet()
メソッドのバイトコードは次のとおりです。
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: getfield
6: nop
7: iconst_m1
8: aload_1
9: monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow
同期メソッドとブロックの大きな違いの 1 つは、同期ブロックは一般にロックの範囲を縮小することです。ロックの範囲はパフォーマンスに反比例するため、コードの重要なセクションのみをロックすることを常にお勧めします。同期ブロックを使用する最良の例の 1 つは、Singleton パターンでのダブル チェック ロックです。メソッド全体をロックする代わりに、getInstance()
Singleton インスタンスの作成に使用されるコードの重要なセクションのみをロックします。ロックが必要なのは 1 回か 2 回だけなので、これによりパフォーマンスが大幅に向上します。
同期メソッドを使用しているときに、静的同期メソッドと非静的同期メソッドの両方を混在させる場合は、特に注意する必要があります。
ほとんどの場合、リストまたはマップへのアクセスを同期するためにこれを使用しますが、オブジェクトのすべてのメソッドへのアクセスをブロックしたくありません。
次のコードでは、リストを変更する 1 つのスレッドは、マップを変更しているスレッドの待機をブロックしません。メソッドがオブジェクトで同期された場合、各メソッドは、それらが行っている変更が競合しない場合でも待機する必要があります。
private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();
public void put( String s, Bar b ) {
synchronized( myMap ) {
myMap.put( s,b );
// then some thing that may take a while like a database access or RPC or notifying listeners
}
}
public void hasKey( String s, ) {
synchronized( myMap ) {
myMap.hasKey( s );
}
}
public void add( Foo f ) {
synchronized( myList ) {
myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
}
}
public Thing getMedianFoo() {
Foo med = null;
synchronized( myList ) {
Collections.sort(myList);
med = myList.get(myList.size()/2);
}
return med;
}
同期ブロックを使用すると、複数のシンクロナイザーを使用できるため、複数の同時であるが競合しないことを同時に実行できます。
同期されたメソッドは、リフレクション API を使用して確認できます。これは、モデル内のすべてのメソッドが同期されているなど、一部のコントラクトをテストするのに役立ちます。
次のスニペットは、Hashtable のすべての同期メソッドを出力します。
for (Method m : Hashtable.class.getMethods()) {
if (Modifier.isSynchronized(m.getModifiers())) {
System.out.println(m);
}
}
多くの場合、メソッド レベルでロックを使用するのは失礼すぎます。メソッド全体をロックして、共有リソースにアクセスしないコードの一部をロックするのはなぜですか。各オブジェクトにはロックがあるため、ダミー オブジェクトを作成してブロック レベルの同期を実装できます。 ブロック レベルは、メソッド全体をロックしないため、より効率的です。
ここでいくつかの例
メソッドレベル
class MethodLevel {
//shared among threads
SharedResource x, y ;
public void synchronized method1() {
//multiple threads can't access
}
public void synchronized method2() {
//multiple threads can't access
}
public void method3() {
//not synchronized
//multiple threads can access
}
}
ブロックレベル
class BlockLevel {
//shared among threads
SharedResource x, y ;
//dummy objects for locking
Object xLock = new Object();
Object yLock = new Object();
public void method1() {
synchronized(xLock){
//access x here. thread safe
}
//do something here but don't use SharedResource x, y
// because will not be thread-safe
synchronized(xLock) {
synchronized(yLock) {
//access x,y here. thread safe
}
}
//do something here but don't use SharedResource x, y
//because will not be thread-safe
}//end of method1
}
[編集]
Collection
同様Vector
に、Hashtable
それらはいつ同期されるかArrayList
、同期HashMap
されないか、および同期キーワードを設定するか、コレクションの同期メソッドを呼び出す必要があります。
Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list
同期メソッドは、すべてのオブジェクトをロックするために使用されます 同期ブロックは、特定のオブジェクトをロックするために使用されます
一般に、これらは、使用されているオブジェクトのモニターと暗黙的な this オブジェクトについて明示的であること以外はほとんど同じです。見過ごされがちな同期メソッドの欠点の 1 つは、"this" 参照を使用して同期する際に、外部オブジェクトが同じオブジェクトをロックする可能性が残されていることです。あなたがそれに遭遇した場合、それは非常に微妙なバグになる可能性があります. 内部の明示的なオブジェクトまたは他の既存のフィールドで同期すると、この問題を回避でき、同期が完全にカプセル化されます。
これは古い質問であることは知っていますが、ここでの回答をざっと読んだだけでは、synchronized
メソッドが間違ったロックである可能性があることに誰も言及していませんでした。
Java Concurrency In Practice (pg. 72) から:
public class ListHelper<E> {
public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...
public syncrhonized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if(absent) {
list.add(x);
}
return absent;
}
上記のコードは、スレッドセーフのように見えます。しかし、実際にはそうではありません。この場合、ロックはクラスのインスタンスで取得されます。ただし、そのメソッドを使用しない別のスレッドによってリストが変更される可能性があります。正しいアプローチは、使用することです
public boolean putIfAbsent(E x) {
synchronized(list) {
boolean absent = !list.contains(x);
if(absent) {
list.add(x);
}
return absent;
}
}
上記のコードは、同期ブロックが完了するまで、リストを変更しようとするすべてのスレッドをブロックして、リストを変更できないようにします。
ここですでに述べたように、同期関数が「this」のみを使用する場合、同期ブロックはロックオブジェクトとしてユーザー定義変数を使用できます。もちろん、同期する必要がある関数の領域を操作することもできます。しかし、「これ」をロックオブジェクトとして機能全体をカバーするブロックと、同期された機能に違いはないと誰もが言います。そうではありません。違いは、両方の状況で生成されるバイト コードにあります。同期ブロックの使用の場合、「this」への参照を保持するローカル変数を割り当てる必要があります。その結果、関数のサイズが少し大きくなります (関数の数が少ない場合は関係ありません)。
ここで見つけることができる違いのより詳細な説明: http://www.artima.com/insidejvm/ed2/threadsynchP.html
スレッドとの同期。1) 動作しないスレッドで synchronized(this) を使用しないでください。(this) と同期すると、現在のスレッドがロック スレッド オブジェクトとして使用されます。各スレッドは他のスレッドから独立しているため、同期の調整は行われません。2) コードのテストでは、Mac 上の Java 1.6 でメソッドの同期が機能しないことが示されています。3) synchronized(lockObj) ここで、lockObj は同期しているすべてのスレッドの共通の共有オブジェクトであり、動作します。4) ReenterantLock.lock() および .unlock() が機能します。これについては、Java チュートリアルを参照してください。
次のコードは、これらの点を示しています。また、ArrayList の代わりに使用されるスレッドセーフな Vector も含まれています。これは、多くのスレッドが Vector に追加しても情報が失われないことを示していますが、ArrayList の場合は情報が失われる可能性があります。0) 現在のコードは、競合状態による情報の損失を示しています。B) ステップ A を逆にして、B のコメントを外し、 // ブロックを終了 }. 次に実行して、データの損失がないことを確認します C) B をコメントアウトし、C のコメントを外します。すべてのバリエーションを完了する時間はありません。これがお役に立てば幸いです。(これ) で同期する場合、またはメソッドの同期が機能する場合は、テストした Java と OS のバージョンをお知らせください。ありがとうございました。
import java.util.*;
/** RaceCondition - Shows that when multiple threads compete for resources
thread one may grab the resource expecting to update a particular
area but is removed from the CPU before finishing. Thread one still
points to that resource. Then thread two grabs that resource and
completes the update. Then thread one gets to complete the update,
which over writes thread two's work.
DEMO: 1) Run as is - see missing counts from race condition, Run severa times, values change
2) Uncomment "synchronized(countLock){ }" - see counts work
Synchronized creates a lock on that block of code, no other threads can
execute code within a block that another thread has a lock.
3) Comment ArrayList, unComment Vector - See no loss in collection
Vectors work like ArrayList, but Vectors are "Thread Safe"
May use this code as long as attribution to the author remains intact.
/mf
*/
public class RaceCondition {
private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
// private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)
private String countLock="lock"; // Object use for locking the raceCount
private int raceCount = 0; // simple add 1 to this counter
private int MAX = 10000; // Do this 10,000 times
private int NUM_THREADS = 100; // Create 100 threads
public static void main(String [] args) {
new RaceCondition();
}
public RaceCondition() {
ArrayList<Thread> arT = new ArrayList<Thread>();
// Create thread objects, add them to an array list
for( int i=0; i<NUM_THREADS; i++){
Thread rt = new RaceThread( ); // i );
arT.add( rt );
}
// Start all object at once.
for( Thread rt : arT ){
rt.start();
}
// Wait for all threads to finish before we can print totals created by threads
for( int i=0; i<NUM_THREADS; i++){
try { arT.get(i).join(); }
catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
}
// All threads finished, print the summary information.
// (Try to print this informaiton without the join loop above)
System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
MAX*NUM_THREADS, raceList.size(), raceCount );
System.out.printf("Array lost %,d. Count lost %,d\n",
MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
} // end RaceCondition constructor
class RaceThread extends Thread {
public void run() {
for ( int i=0; i<MAX; i++){
try {
update( i );
} // These catches show when one thread steps on another's values
catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
catch( OutOfMemoryError oome ) { System.out.print("O"); }
}
}
// so we don't lose counts, need to synchronize on some object, not primitive
// Created "countLock" to show how this can work.
// Comment out the synchronized and ending {, see that we lose counts.
// public synchronized void update(int i){ // use A
public void update(int i){ // remove this when adding A
// synchronized(countLock){ // or B
// synchronized(this){ // or C
raceCount = raceCount + 1;
raceList.add( i ); // use Vector
// } // end block for B or C
} // end update
} // end RaceThread inner class
} // end RaceCondition outter class