【知識の蓄積】java synchronized

6845 ワード

Java言語には、同期ブロック(またはメソッド)とvolatile変数の2つの内在的な同期メカニズムが含まれています.この2つのメカニズムの提案は,コードスレッドのセキュリティを実現するためである.ここで、Volatile変数は同期性が悪い(ただし、より簡単でオーバーヘッドが低い場合がある)ため、エラーが発生しやすい.ここで、同期ブロック(またはメソッド)は、キーワードsynchronizedまたはjava.util.concurrent.lockのクラスReentrantLockを使用することができる.
ここではsynchronizedのみを紹介し、その他は続々と紹介します.
(1):synchronizedなどで同期する理由.
これは主にjavaメモリモデル(JMMと略称する)と、主メモリ領域(一般的にスタック)と作業メモリ領域に分けられ、javaスレッドの間には見えない、すなわち作業メモリ(nスレッドにn個の作業メモリが存在する)は独立しており、関連する変数の変化はすべて主メモリ(あり、1個のみ)によって行われる.すべての変数と変数はメインメモリ領域に存在し、スレッドが変数を使用する場合は次の操作を行います.
1.メインメモリからワークメモリにデータをコピーする2.コードを実行し、データに対して各種操作と計算を行う3.操作後の変数値をメインメモリに書き換える
もちろん、このような運行順序も期待されています.しかし、JVMは、第1ステップと第3ステップが上記の手順に従って直ちに実行されることを保証していません.java言語規範の規定に従って、スレッドのワークメモリとホストメモリ間のデータ交換は緩やかに結合されているため、いつワークメモリを更新する必要があるか、あるいはいつホストメモリの内容を更新する必要があるかは、体の仮想マシンは自己決定を実現する.JVMは特徴コードをチューニングすることができるため、いくつかの実行手順の順序の逆転を変える.それではスレッドが変数を呼び出すたびに自分のワークメモリの値を直接取るか、メインメモリのcopyから先に取るかは保証されていないので、いずれの場合も発生する可能性がある.同様に、スレッドが変数の値を変更した後、すぐにメインメモリに戻すかどうかは保証できません.すぐに書くかもしれません.しばらくしてから書くかもしれません.では、マルチスレッドのアプリケーションシーンで問題が発生します.複数のスレッドが同じコードブロックに同時にアクセスすると、あるスレッドが変数の値を変更した可能性があります.もちろん、現在の変更はワークメモリの変更に限られています.このときJVM変更後の値をすぐにメインメモリに書き込むことは保証されません.つまり、他のスレッドがすぐに変更後の値を得ることができず、古い変数で様々な操作や演算を行い、最終的に予想できない結果を招く可能性があります.
synchronizedキーワードは、保護されたコードブロックが同じ時間に1つのスレッドしか入らず実行できないように、反発ロックを強制的に実施します.もちろんsynchronizedには、スレッドがsynchronizedブロックに入る前に、ワークメモリ内のすべてのコンテンツをメインメモリにマッピングし、ワークメモリを空にしてメインメモリから最新の値をコピーします.スレッドがsynchronizedブロックを終了すると、同じようにワークメモリの値がプライマリメモリにマッピングされますが、ワークメモリが空になることはありません.これにより、スレッドがコードブロックを実行した後、ワークメモリの値とプライマリメモリの値が一致し、データの一貫性が保証されるように、上記の順序で実行を強制できます.
(2):synchronizedの使い方
2.1:synchronized修飾方法.
サンプルコードは次のとおりです.
          
public synchronized void getVal(int Val); 

Javaでは、各クラスインスタンスはロックに対応し、各synchronizedメソッドは、そのメソッドを呼び出すクラスインスタンスのロックを取得する必要があります.
実行します.そうでなければ、属するスレッドがブロックされ、メソッドが実行されると、ロックが排他され、メソッドから戻るまでロックが解放されます.その後、ブロックされたスレッドはロックを取得し、実行可能な状態に戻ります.このメカニズムは、synchronizedと宣言されたすべてのメンバー関数のうち、synchronizedが実行可能であることを保証します.(クラスインスタンスに対応するロックが1つしか得られないため)、クラスメンバー変数へのアクセス競合を効果的に回避します(クラスメンバー変数へのアクセス可能なすべてのメソッドがsynchronizedとして宣言されている限り).
注意:Javaでは、クラスインスタンスだけでなく、各クラスもロックに対応しています.これにより、クラスの静的メンバー関数をsynchronizedとして宣言して、クラスの静的メンバー変数へのアクセスを制御することもできます.
 
class testSynchronized{
   private int Val;
   public synchronized void increaseVal( ){
       Val++;
   }
  public synchronized void decreaseVal( ){
       Val--;
   }
  
}

 
2.2:synchronizedキーワードでsynchronizedブロックを宣言します.構文は次のとおりです.
synchronized(syncObject) { 
//          
} 

 
synchronizedブロックは、前述したようにクラスインスタンスまたはクラスであってもよいオブジェクトsyncObjectのロックを取得しなければならないコードブロックであり、具体的なメカニズムは前述したとおりである.任意のコードブロックに対して、ロックされたオブジェクトを任意に指定できるため、柔軟性が高い.
2.3:synchronizedキーワードによる静的修飾方法:static synchronized method(){}
2.4:synchronizedによるクラスロックの取得:synchronized(classname.class)
注意:
前の2つのロックはオブジェクトモニタであり、後の2つのロックはクラスモニタであり、いずれも反発アクセスを実現することができる.1つのオブジェクトには1つのオブジェクトモニタしかなく、1つのクラスにも1つのクラスモニタしかない.静的な方法はクラスモニタを使用して同期し、一般的な方法はオブジェクトモニタを使用して同期する.
(3)synchronizedブロックとメソッドアクセスには、次のルールがあります.
一、2つの同時スレッドが同じオブジェクトobjectのこのsynchronized同期コードブロックまたはメソッドにアクセスする場合、1つの時間内に1つのスレッドしか実行できない.もう1つのスレッドは、現在のスレッドがこのコードブロックを実行するのを待ってから、そのコードブロックを実行しなければならない.2、1つのスレッドがobjectの1つのsynchronized同期コードブロックまたはメソッドにアクセスする場合、もう1つのスレッドは、objectの1つのsynchronized同期コードブロックまたはメソッドを実行する.スレッドは、object内の非synchronized同期コードブロックまたはメソッドにもアクセスできます.3、1つのスレッドがobjectのsynchronized同期コードブロックまたはメソッドにアクセスすると、そのobjectのオブジェクトロックが取得され、他のスレッドがobject内の他のすべてのsynchronized同期コードブロックまたはメソッドへのアクセスがブロックされます.4、以上のルールは、他のオブジェクトにロックされます.サンプルが適用されます.
ロックはオブジェクトに関連付けられており、各オブジェクトにはロックがあります.synchronized文を実行するには、スレッドはsynchronized文で式で指定されたオブジェクトのロックを取得する必要があります.1つのオブジェクトにはロックが1つしかありません.1つのスレッドによって取得された後、このロックはなくなり、スレッドはsynchronized文を実行した後、ロックをオブジェクトに返します.
メソッドの前にsynchronized修飾子を付けると、メソッドを同期化メソッドとして宣言できます.同期化メソッドは実行前にロックを取得します.これがクラスメソッドの場合、取得したロックは、メソッドのクラスに関連するClassクラスオブジェクトのロックです.これがインスタンスメソッドの場合、このロックはthisオブジェクトのロックです.
インスタンス分析
package com.ailk.sms;

public class TestSynchronized  {
	int k=0;
	
	public static void main(String args[]){
		
		final TestSynchronized ts=new TestSynchronized();
		new Thread(){			
			public void run(){
				
				while( true ){
					System.out.println( "   ");
					ts.operate(  );
				}
					//this.currentThread().sel

			}
		}.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Thread t2=new Thread(){
			
			public void run(){
				
				while( true  ){
					System.out.println( "   ");
					ts.printK();
				}
			}
		};

		t2.start();
		
		new Thread(){
			
			public void run(){
				
				while( true  ){
					System.out.println( "   ");
					ts.printK1();
				}
			}
		}.start();
		
	}
   
	synchronized public void operate( ) {

		System.out.println( "operate "+k );
		while( true ){
		//	this.k=k++;
			k=10000;
		}
	}
	
	synchronized	public void printK(){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println( "printK "+k );
		
	}
	public void printK1(){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println( "printK1 "+k );
		
	}
}

実行結果は
   
operate 0
   
   
printK1 10000
   
printK1 10000
   
printK1 10000
   
printK1 10000

分析:
(1):このクラスTestSynchronizedには3つの関数があり、2つの関数にはsynchronized修飾がそれぞれoperateであり、printKにはsynchronizedの一般的な関数は含まれていない.
(2):mainメソッドには、TestSynchronizedをそれぞれ呼び出す3つのスレッドの関数が含まれています.
(3):実行結果はスレッドがスレッド1を先に実行し、スレッド1がoperateメソッドを実行し、クラスTestSynchronizedオブジェクトのロックオブジェクトを維持するが、このメソッドはデッドループであるため、スレッド1がTestSynchronizedオブジェクトのロックを維持し続けることでファイルが生成され、スレッド2がTestSynchronizedオブジェクトロックを取得できずにブロックされる【2つの同時スレッドが同じオブジェクトobjectのこのsynchronized同期コードブロックまたはメソッドにアクセスする場合、1つの時間内に1つのスレッドしか実行できません.別のスレッドは、現在のスレッドがこのコードブロックを実行するまで待たなければなりません】一方、printK 1の一般的な方法では、TestSynchronizedオブジェクトのロックは必要ありませんので、通常の出力【あるスレッドがobjectのsynchronized同期コードブロックまたはメソッドにアクセスする場合、別のスレッドは、objectの非synchronized同期コードブロックまたはメソッドにアクセスすることができる.】