java synchronized what is thread synchronization java
このチュートリアルでは、Javaでのスレッド同期と、Javaロック、競合状態、ミューテックス、Java揮発性、Javaでのデッドロックなどの関連概念について説明します。
複数のスレッドが関係するマルチスレッド環境では、複数のスレッドが同時に同じリソースを取得しようとすると、衝突が発生する可能性があります。これらの衝突により「競合状態」が発生するため、プログラムは予期しない結果を生成します。
例えば、 1つのファイルが2つのスレッドによって更新されています。 1つのスレッドT1がこのファイルを更新している場合は、変数を言います。 T1によるこの更新がまだ進行中の間に、2番目のスレッドT2も同じ変数を更新するとします。このように、変数は間違った結果をもたらします。
=> ここで完全なJavaトレーニングシリーズをご覧ください。
複数のスレッドが関係する場合、一度に1つのスレッドからリソースにアクセスできるようにこれらのスレッドを管理する必要があります。上記の例では、両方のスレッドによってアクセスされるファイルは、T1がファイルへのアクセスを完了するまでT2がファイルにアクセスできないように管理する必要があります。
これは、Javaで「 スレッドの同期 」。
学習内容:
Javaでのスレッド同期
Javaはmulti_threaded言語であるため、アプリケーションでは複数のスレッドが並行して実行されるため、Javaではスレッドの同期が非常に重要です。
キーワードを使用します 「同期」 そして 「揮発性」 Javaで同期を実現する
共有オブジェクトまたはリソースが変更可能である場合、同期が必要です。リソースが不変である場合、スレッドはリソースを同時にまたは個別に読み取るだけです。
この場合、リソースを同期する必要はありません。この場合、JVMは次のことを保証します。 Java同期コードは、一度に1つのスレッドによって実行されます 。
ほとんどの場合、Javaの共有リソースへの同時アクセスにより、「メモリの不整合」や「スレッドの干渉」などのエラーが発生する可能性があります。これらのエラーを回避するには、共有リソースの同期を行って、これらのリソースへのアクセスが相互に排他的であるようにする必要があります。
私たちはという概念を使用します 同期を実装するためのモニター。 モニターには、一度に1つのスレッドのみがアクセスできます。スレッドがロックを取得すると、スレッドがモニターに入ったと言えます。
モニターが特定のスレッドによってアクセスされている場合、モニターはロックされ、モニターに入ろうとしている他のすべてのスレッドは、アクセスしているスレッドが終了してロックを解除するまで中断されます。
今後、このチュートリアルではJavaでの同期について詳しく説明します。それでは、Javaでの同期に関連するいくつかの基本的な概念について説明しましょう。
Javaでの競合状態
マルチスレッド環境では、複数のスレッドが同時に書き込みを行うために共有リソースにアクセスしようとすると、複数のスレッドが互いに競合してリソースへのアクセスを終了します。これにより、「競合状態」が発生します。
考慮すべきことの1つは、複数のスレッドが読み取り専用で共有リソースにアクセスしようとしても問題がないということです。この問題は、複数のスレッドが同じリソースに同時にアクセスする場合に発生します。
競合状態は、プログラム内のスレッドの適切な同期が不足しているために発生します。一度に1つのスレッドだけがリソースにアクセスするようにスレッドを適切に同期すると、競合状態は存在しなくなります。
では、どのようにして競合状態を検出するのでしょうか?
競合状態を検出する最良の方法は、コードレビューによるものです。プログラマーとして、コードを徹底的にレビューして、発生する可能性のある潜在的な競合状態をチェックする必要があります。
Javaでのロック/モニター
同期を実装するためにモニターまたはロックを使用することはすでに説明しました。モニターまたはロックは内部エンティティであり、すべてのオブジェクトに関連付けられています。したがって、スレッドがオブジェクトにアクセスする必要があるときはいつでも、最初にそのオブジェクトのロックまたはモニターを取得し、オブジェクトを操作してから、ロックを解放する必要があります。
Javaのロックは次のようになります。
public class Lock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while(isLocked) { wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } } 上に示したように、インスタンスをロックするlock()メソッドがあります。 lock()メソッドを呼び出すすべてのスレッドは、unblock()メソッドがlockedフラグをfalseに設定し、待機中のすべてのスレッドに通知するまでブロックされます。
ロックについて覚えておくべきいくつかのポインタ:
- Javaでは、各オブジェクトにロックまたはモニターがあります。このロックにはスレッドからアクセスできます。
- 一度に1つのスレッドのみがこのモニターまたはロックを取得できます。
- Javaプログラミング言語には、Synchronizedというキーワードが用意されており、ブロックまたはメソッドをSynchronizedとして作成することでスレッドを同期できます。
- スレッドがアクセスする必要のある共有リソースは、この同期されたブロック/メソッドの下に保持されます。
Javaのミューテックス
マルチスレッド環境では、複数のスレッドが共有リソースに同時にアクセスしようとすると競合状態が発生し、競合状態によって予期しない出力が発生する可能性があることはすでに説明しました。
共有リソースにアクセスしようとするプログラムの部分は、 「クリティカルセクション」 。競合状態の発生を回避するには、クリティカルセクションへのアクセスを同期する必要があります。このクリティカルセクションを同期することにより、一度に1つのスレッドのみがクリティカルセクションにアクセスできるようにします。
最も単純なタイプのシンクロナイザーは「ミューテックス」です。ミューテックスは、任意のインスタンスで、1つのスレッドのみがクリティカルセクションを実行できることを保証します。
ミューテックスは、上記で説明したモニターまたはロックの概念に似ています。スレッドがクリティカルセクションにアクセスする必要がある場合は、ミューテックスを取得する必要があります。ミューテックスが取得されると、スレッドはクリティカルセクションコードにアクセスし、完了するとミューテックスを解放します。
クリティカルセクションへのアクセスを待機している他のスレッドは、その間ブロックされます。ミューテックスを保持しているスレッドがそれを解放するとすぐに、別のスレッドがクリティカルセクションに入ります。
テスト用の無料のSOAPWebサービス
Javaでミューテックスを実装する方法はいくつかあります。
- 同期されたキーワードの使用
- セマフォの使用
- ReentrantLockの使用
このチュートリアルでは、最初のアプローチ、つまり同期について説明します。他の2つのアプローチ– SemaphoreとReentrantLockについては、次のチュートリアルで説明します。ここでは、Java並行パッケージについて説明します。
同期されたキーワード
Javaには、プログラムでクリティカルセクションをマークするために使用できる「同期済み」というキーワードが用意されています。クリティカルセクションは、コードのブロックまたは完全なメソッドにすることができます。したがって、Synchronizedキーワードでマークされたクリティカルセクションにアクセスできるのは1つのスレッドだけです。
Synchronizedキーワードを使用して、アプリケーションの並行パーツ(同時に実行されるパーツ)を作成できます。また、コードのブロックまたはメソッドをSynchronizedにすることで、競合状態を取り除きます。
ブロックまたはメソッドを同期済みとしてマークすると、これらのエンティティ内の共有リソースが同時アクセスとそれによる破損から保護されます。
同期の種類
以下に説明するように、同期には2つのタイプがあります。
#1)プロセスの同期
プロセス同期には、同時に実行される複数のプロセスまたはスレッドが含まれます。最終的には、これらのプロセスまたはスレッドが特定の一連のアクションにコミットする状態になります。
#2)スレッドの同期
スレッド同期では、複数のスレッドが共有スペースにアクセスしようとしています。スレッドは、共有スペースが一度に1つのスレッドによってのみアクセスされるように同期されます。
プロセス同期は、このチュートリアルの範囲外です。したがって、ここではスレッド同期についてのみ説明します。
Javaでは、次のように同期キーワードを使用できます。
- コードのブロック
- 方法
上記のタイプは、相互に排他的なタイプのスレッド同期です。相互排除は、共有データにアクセスするスレッドが相互に干渉するのを防ぎます。
もう1つのタイプのスレッド同期は、スレッド間の連携に基づく「スレッド間通信」です。スレッド間通信は、このチュートリアルの範囲外です。
ブロックとメソッドの同期を進める前に、同期がない場合のスレッドの動作を示すJavaプログラムを実装しましょう。
同期なしのマルチスレッド
次のJavaプログラムには、同期されていない複数のスレッドがあります。
class PrintCount { //method to print the thread counter public void printcounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run method for thread public void run() { PD.printcounter(); System.out.println('Thread ' + threadName + ' exiting.'); } //start method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args()) { PrintCount PD = new PrintCount(); //create two instances of thread class ThreadCounter T1 = new ThreadCounter( 'ThreadCounter_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'ThreadCounter_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }出力

出力から、スレッドが同期されていないため、出力に一貫性がないことがわかります。両方のスレッドが開始し、次にカウンターが次々に表示されます。両方のスレッドは最後に終了します。
指定されたプログラムから、最初のスレッドはカウンター値を表示した後に終了し、2番目のスレッドはカウンター値の表示を開始する必要があります。
4年の経験のためのセレンインタビューの質問
それでは、同期に取り掛かり、コードブロックの同期から始めましょう。
同期されたコードブロック
同期ブロックは、コードのブロックを同期するために使用されます。このブロックは通常、数行で構成されます。同期ブロックは、メソッド全体を同期させたくない場合に使用されます。
例えば、 たとえば75行のコードを持つメソッドがあります。このうち、一度に1つのスレッドで実行する必要があるのは10行のコードだけです。この場合、メソッド全体を同期化すると、システムに負担がかかります。このような状況では、同期ブロックを使用します。
同期メソッドのスコープは、常に同期メソッドのスコープよりも小さくなります。同期メソッドは、複数のスレッドによって使用される共有リソースのオブジェクトをロックします。
同期ブロックの一般的な構文は次のとおりです。
synchronized (lock_object){ //synchronized code statements }ここで、「lock_object」は、ロックを取得するオブジェクト参照式です。したがって、スレッドが実行のためにブロック内の同期されたステートメントにアクセスする場合は常に、「lock_object」モニターのロックを取得する必要があります。
すでに説明したように、synchronizedキーワードは、一度に1つのスレッドのみがロックを取得でき、他のすべてのスレッドは、ロックを保持しているスレッドが終了してロックを解放するまで待機する必要があることを保証します。
注意
- 使用されるlock_objectがNullの場合、「NullPointerException」がスローされます。
- ロックを保持したままスレッドがスリープしている場合、ロックは解放されません。このスリープ時間中、他のスレッドは共有オブジェクトにアクセスできなくなります。
ここで、わずかな変更を加えてすでに実装されている上記の例を示します。以前のプログラムでは、コードを同期しませんでした。次に、同期ブロックを使用して出力を比較します。
同期を使用したマルチスレッド
以下のJavaプログラムでは、同期ブロックを使用しています。 runメソッドでは、各スレッドのカウンターを出力する行のコードを同期します。
class PrintCount { //print thread counter public void printCounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run () method for thread with synchronized block public void run() { synchronized(PD) { PD.printCounter(); } System.out.println('Thread ' + threadName + ' exiting.'); } //start () method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args()) { PrintCount PD = new PrintCount(); //create thread instances ThreadCounter T1 = new ThreadCounter( 'Thread_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'Thread_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }出力

これで、同期ブロックを使用したこのプログラムの出力は非常に一貫しています。予想どおり、両方のスレッドが実行を開始します。最初のスレッドはカウンター値の表示を終了し、終了します。次に、2番目のスレッドがカウンター値を表示して終了します。
同期メソッド
このセクションでは、同期方法について説明します。以前、より少ないコード行で構成される小さなブロックを同期ブロックとして宣言できることを確認しました。関数全体を同期させたい場合は、メソッドを同期済みとして宣言できます。
メソッドが同期されると、一度に1つのスレッドのみがメソッド呼び出しを行うことができます。
同期メソッドを作成するための一般的な構文は次のとおりです。
synchronized method_name (parameters){ //synchronized code } 同期ブロックと同様に、同期メソッドの場合、同期メソッドにアクセスするスレッドによって使用されるlock_objectが必要です。
同期メソッドの場合、ロックオブジェクトは次のいずれかになります。
- 同期されたメソッドが静的である場合、ロックオブジェクトは「.class」オブジェクトによって指定されます。
- 非静的メソッドの場合、ロックオブジェクトは現在のオブジェクト、つまり「this」オブジェクトによって指定されます。
同期されたキーワードの独特の特徴は、それが再入可能であるということです。これは、同期されたメソッドが同じロックを持つ別の同期されたメソッドを呼び出すことができることを意味します。したがって、ロックを保持しているスレッドは、別のロックを取得しなくても、別の同期メソッドにアクセスできます。
同期メソッドは、以下の例を使用して示されます。
class NumberClass { //synchronized method to print squares of numbers synchronized void printSquares(int n) throws InterruptedException { //iterate from 1 to given number and print the squares at each iteration for (int i = 1; i <= n; i++) { System.out.println(Thread.currentThread().getName() + ' :: '+ i*i); Thread.sleep(500); } } } public class Main { public static void main(String args()) { final NumberClass number = new NumberClass(); //create thread Runnable thread = new Runnable() { public void run() { try { number.printSquares(3); } catch (InterruptedException e) { e.printStackTrace(); } } }; //start thread instance new Thread(thread, 'Thread One').start(); new Thread(thread, 'Thread Two').start(); } } 出力

上記のプログラムでは、同期メソッドを使用して数値の二乗を出力しました。数の上限は引数としてメソッドに渡されます。次に、1から始まり、上限に達するまで各数値の2乗が印刷されます。
main関数では、スレッドインスタンスが作成されます。各スレッドインスタンスには、正方形を印刷するための数値が渡されます。
上記のように、同期されるメソッドが静的である場合、ロックオブジェクトはオブジェクトではなくクラスに含まれます。これは、オブジェクトではなくクラスをロックすることを意味します。これは静的同期と呼ばれます。
別の例を以下に示します。
class Table{ //synchronized static method to print squares of numbers synchronized static void printTable(int n){ for(int i=1;i<=10;i++){ System.out.print(n*i + ' '); try{ Thread.sleep(400); }catch(Exception e){} } System.out.println(); } } //thread class Thread_One class Thread_One extends Thread{ public void run(){ Table.printTable(2); } } //thread class Thread_Two class Thread_Two extends Thread{ public void run(){ Table.printTable(5); } } public class Main{ public static void main(String t()){ //create instances of Thread_One and Thread_Two Thread_One t1=new Thread_One (); Thread_Two t2=new Thread_Two (); //start each thread instance t1.start(); t2.start(); } } 出力

上記のプログラムでは、数の掛け算の九九を印刷します。テーブルが出力される各番号は、異なるスレッドクラスのスレッドインスタンスです。したがって、2と5の掛け算の九九を出力するので、テーブル2と5をそれぞれ出力する2つのクラスのthread_oneとthread_twoがあります。
要約すると、Java同期キーワードは次の機能を実行します。
- Javaのsynchronizedキーワードは、ロックメカニズムを提供することにより、共有リソースへの相互に排他的なアクセスを保証します。ロックは競合状態も防ぎます。
- 同期キーワードを使用して、コードでの同時プログラミングエラーを防ぎます。
- メソッドまたはブロックが同期済みとして宣言されている場合、スレッドは同期済みのメソッドまたはブロックに入るには排他ロックが必要です。必要なアクションを実行した後、スレッドはロックを解放し、書き込み操作をフラッシュします。このようにして、不整合に関連するメモリエラーを排除します。
Javaで揮発性
Javaのvolatileキーワードは、クラスをスレッドセーフにするために使用されます。また、volatileキーワードを使用して、さまざまなスレッドによって変数値を変更します。 volatileキーワードを使用して、オブジェクトだけでなくプリミティブ型を持つ変数を宣言できます。
場合によっては、volatileキーワードが同期キーワードの代わりに使用されますが、同期キーワードの代わりにはならないことに注意してください。
変数が揮発性として宣言されている場合、その値はキャッシュされませんが、常にメインメモリから読み取られます。揮発性変数は、順序付けと可視性を保証します。変数を揮発性として宣言することはできますが、クラスまたはメソッドを揮発性として宣言することはできません。
次のコードブロックについて考えてみます。
class ABC{ static volatile int myvar =10; }上記のコードでは、変数myvarは静的で揮発性です。静的変数は、すべてのクラスオブジェクト間で共有されます。揮発性変数は常にメインメモリに存在し、キャッシュされることはありません。
したがって、メインメモリにはmyvarのコピーが1つだけあり、すべての読み取り/書き込みアクションはメインメモリからこの変数に対して実行されます。 myvarが揮発性として宣言されていない場合、各スレッドオブジェクトには異なるコピーがあり、不整合が発生します。
VolatileキーワードとSynchronizedキーワードの違いのいくつかを以下に示します。
| 揮発性キーワード | 同期されたキーワード |
|---|---|
| volatileキーワードは、変数でのみ使用されます。 | 同期キーワードは、コードブロックおよびメソッドで使用されます。 |
| volatileキーワードは、待機中のスレッドをブロックできません。 | 同期されたキーワードは、待機中のスレッドをブロックできます。 |
| Volatileを使用すると、スレッドのパフォーマンスが向上します。 | スレッドのパフォーマンスは、同期すると多少低下します。 |
| 揮発性変数はメインメモリに存在します。 | 同期された構成はメインメモリに存在しません。 |
| Volatileは、スレッドメモリとメインメモリの間で一度に1つの変数を同期します。 | Synchronizedキーワードは、すべての変数を一度に同期します。 |
Javaでのデッドロック
synchronizedキーワードを使用して複数のスレッドを同期し、プログラムをスレッドセーフにすることができることを確認しました。スレッドを同期することにより、マルチスレッド環境で複数のスレッドが同時に実行されるようにします。
ただし、スレッドが同時に機能できなくなる状況が発生する場合があります。代わりに、彼らは際限なく待ちます。これは、1つのスレッドがリソースを待機し、そのリソースが2番目のスレッドによってブロックされた場合に発生します。
一方、2番目のスレッドは、最初のスレッドによってブロックされているリソースを待機しています。このような状況は、Javaで「デッドロック」を引き起こします。
Javaでのデッドロックは、以下の画像を使用して示されています。

上の図からわかるように、スレッドAはリソースr1をロックし、リソースr2を待機しています。一方、スレッドBはリソースr2をブロックし、r1を待機しています。
したがって、保留中のリソースを取得しない限り、どのスレッドも実行を終了できません。この状況により、両方のスレッドがリソースを際限なく待機するというデッドロックが発生しました。
以下に示すのは、Javaでのデッドロックの例です。
public class Main { public static void main(String() args) { //define shared resources final String shared_res1 = 'Java tutorials'; final String shared_res2 = 'Multithreading'; // thread_one => locks shared_res1 then shared_res2 Thread thread_one = new Thread() { public void run() { synchronized (shared_res1) { System.out.println('Thread one: locked shared resource 1'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res2) { System.out.println('Thread one: locked shared resource 2'); } } } }; // thread_two=> locks shared_res2 then shared_res1 Thread thread_two = new Thread() { public void run() { synchronized (shared_res2) { System.out.println('Thread two: locked shared resource 2'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res1) { System.out.println('Thread two: locked shared resource 1'); } } } }; //start both the threads thread_one.start(); thread_two.start(); } }出力

上記のプログラムでは、2つの共有リソースと2つのスレッドがあります。両方のスレッドが共有リソースに1つずつアクセスしようとします。出力には、他のスレッドが待機している間、両方のスレッドがそれぞれ1つのリソースをロックしていることが示されています。これにより、デッドロック状態が発生します。
デッドロック状態の発生を完全に阻止することはできませんが、いくつかの手順を実行することで確実に回避できます。
以下に、Javaでのデッドロックを回避するための手段を示します。
#1)ネストされたロックを回避する
ネストされたロックを持つことは、デッドロックを持つ最も重要な理由です。ネストされたロックは、複数のスレッドに与えられるロックです。したがって、複数のスレッドにロックを与えることは避けてください。
#2)スレッド結合を使用する
スレッドが実行に最大時間を使用できるように、最大時間でThread.joinを使用する必要があります。これにより、1つのスレッドが他のスレッドを継続的に待機するときに主に発生するデッドロックを防ぐことができます。
#3)不要なロックを避ける
必要なコードだけをロックする必要があります。コードに不要なロックがあると、プログラムでデッドロックが発生する可能性があります。デッドロックはコードを破壊し、プログラムのフローを妨げる可能性があるため、プログラムのデッドロックを回避する傾向があります。
よくある質問
Q#1)同期とは何ですか?なぜそれが重要なのですか?
回答: 同期は、複数のスレッドへの共有リソースのアクセスを制御するプロセスです。同期がないと、複数のスレッドが共有リソースを同時に更新または変更して、不整合が発生する可能性があります。
したがって、マルチスレッド環境では、スレッドが同期され、共有リソースにアクセスする方法が相互に排他的で一貫していることを確認する必要があります。
Q#2)Javaの同期と非同期とは何ですか?
回答: 同期とは、構成がスレッドセーフであることを意味します。これは、複数のスレッドが一度に構成(コードブロック、メソッドなど)にアクセスできないことを意味します。
同期されていない構成はスレッドセーフではありません。複数のスレッドが、同期されていないメソッドまたはブロックにいつでもアクセスできます。 Javaで人気のある非同期クラスはStringBuilderです。
Q#3)なぜ同期が必要なのですか?
回答: プロセスを同時に実行する必要がある場合は、同期が必要です。これは、多くのプロセス間で共有できるリソースが必要なためです。
共有リソースにアクセスするためのプロセスまたはスレッド間の衝突を回避するには、これらのリソースを同期して、すべてのスレッドがリソースにアクセスし、アプリケーションもスムーズに実行されるようにする必要があります。
Q#4)Synchronized ArrayListを取得するにはどうすればよいですか?
回答: ArrayListを同期リストに変換するための引数としてArrayListを指定したCollections.synchronizedlistメソッドを使用できます。
Q#5)HashMapは同期されていますか?
グーグルウェブマスターツール壊れたリンクチェッカー
回答: いいえ、HashMapは同期されていませんが、HashTableは同期されています。
結論
このチュートリアルでは、スレッドの同期について詳しく説明しました。それに加えて、Javaのvolatileキーワードとデッドロックについても学びました。同期は、プロセスとスレッドの同期で構成されます。
マルチスレッド環境では、スレッドの同期に関心があります。ここでは、スレッド同期の同期キーワードアプローチを見てきました。
デッドロックは、複数のスレッドがリソースを際限なく待機する状況です。 Javaでのデッドロックを回避する方法とともに、Javaでのデッドロックの例を見てきました。
=> ゼロからJavaを学ぶには、ここにアクセスしてください。