synchronizedに関するいくつかの小さな実験

7437 ワード

一、準備作業:
1、新規に次の流水番号を取得する工具類
public class IdTool {

	private static int currentIdValue = 0;
	
	public static int getNextId(){
		
		currentIdValue = currentIdValue+1;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return currentIdValue;
	}
}

 
2、新規試験クラス
public class Test extends Thread {

	@Override
	public void run() {
		for(int i=0;i<3;i++){		
			System.out.println(Thread.currentThread().getName()
					+",getNextId()="+IdTool.getNextId());
		}
	}	
	public static void main(String[] args) {

		Test t1 = new Test();
		Test t2 = new Test();
		
		t1.start();
		t2.start();
	}
}

 
二、シーン分けテスト
1、synchronizedキーワードを付けないシーン、すなわち上のコードは、何の修正もせず、結果を実行する.
Thread-0,getNextId()=2
Thread-1,getNextId()=2
Thread-0,getNextId()=4
Thread-1,getNextId()=4
Thread-1,getNextId()=6
Thread-0,getNextId()=6

 
小結:マルチスレッドシーンでは、パブリックリソースへのアクセス、変更について、スレッド同期処理を行わないと、データの混乱が発生する可能性があります.
 
2、staticメソッドsynchronizedのシーンを追加し、getNextId()メソッドにsynchronized修飾子を追加し、修正したコードは以下の通りです.
public class IdTool {

	private static int currentIdValue = 0;
	
	public static synchronized int getNextId(){
		
		currentIdValue = currentIdValue+1;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return currentIdValue;
	}
}

 
次のように再実行されます.
Thread-0,getNextId()=1
Thread-1,getNextId()=2
Thread-0,getNextId()=3
Thread-1,getNextId()=4
Thread-0,getNextId()=5
Thread-1,getNextId()=6

 
まとめ:synchronizedキーワードを加えると、データは正常になります.
 
3、synchronizedはコードブロック(オブジェクトロックは現在のクラスのclassオブジェクト)のシーンに適用され、修正されたgetNextId()の方法は以下の通りである.
public class IdTool {

	private static int currentIdValue = 0;
	
	public static  int getNextId(){
		synchronized(IdTool.class){			
			currentIdValue = currentIdValue+1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return currentIdValue;
		}
	}
}

 
次のように再実行されます.
Thread-0,getNextId()=1
Thread-1,getNextId()=2
Thread-0,getNextId()=3
Thread-1,getNextId()=4
Thread-0,getNextId()=5
Thread-1,getNextId()=6

 
まとめ:同期コードブロックは、スレッドセキュリティの問題を解決することもできます.
補足:synchronized(IdTool.class){}コードブロックはpublic static synchronizedに等価であり、ロックオブジェクトは現在のクラスオブジェクトである.
 
4、synchronizedはコードブロック(オブジェクトロックはサードパーティオブジェクト)のシーンに適用され、修正されたgetNextId()の方法は以下の通りである.
public class IdTool {

	private static int currentIdValue = 0;
	private static byte[] byteObj = new byte[0];
	
	public static  int getNextId(){
		synchronized(byteObj){			
			currentIdValue = currentIdValue+1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return currentIdValue;
		}
	}
}

 
長さ0のバイト配列を使うのは、このようなオブジェクトが最も軽量なオブジェクトであり、Object obj=new Object()よりも大きいからだと言われています.まだ小さいです.
次のように再実行されます.
Thread-0,getNextId()=1
Thread-1,getNextId()=2
Thread-0,getNextId()=3
Thread-1,getNextId()=4
Thread-0,getNextId()=5
Thread-1,getNextId()=6

 
小結:共有リソースがロックされたオブジェクトとして使用できない場合は、サードパーティ製オブジェクトをロックされたオブジェクトとして使用できます.
 
5、synchronizedはコードブロック(一時的にオブジェクトロックを作成)のシーンに適用され、修正されたgetNextId()の方法は以下の通りである.
public class IdTool {

	private static int currentIdValue = 0;
	
	public static  int getNextId(){
		synchronized(new byte[0]){			
			currentIdValue = currentIdValue+1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return currentIdValue;
		}
	}
}

 
次のように再実行されます.
Thread-0,getNextId()=2
Thread-1,getNextId()=2
Thread-0,getNextId()=4
Thread-1,getNextId()=4
Thread-0,getNextId()=6
Thread-1,getNextId()=6

 
小結:各スレッドが同期ブロックに到達すると、ロック対象のロックが要求されます.ここでのロック対象は動的であるため、すなわち、各スレッドが来ると、ロック対象が作成され、ロックされます.したがって、各スレッドが来ると、ロックは成功します.さらに、このようなオブジェクトロックは意味がなく、スレッドセキュリティの問題を解決できません.
 
6、オブジェクトロックはクラスオブジェクトであり、このクラスには複数の同期方法のシーンが存在する.
6.1、新しい公共資源類.
import java.util.Date;

public class PublicResource {

	public static synchronized void method1(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new Date()+" "+Thread.currentThread().getName()+", method1");
	}
	
	public static synchronized void  method2(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new Date()+" "+Thread.currentThread().getName()+", method2");
	}
}

6.2、新規2スレッドクラス
public class Thread1 extends Thread {

	@Override
	public void run() {
		PublicResource.method1();
	}	
}

 
public class Thread2 extends Thread {

	@Override
	public void run() {
		PublicResource.method2();
	}	
}

 
6.3、新規試験クラス
public class Test {

	public static void main(String[] args) {
		
		Thread1 t1 = new Thread1();
		Thread2 t2 = new Thread2();
		
		t1.start();
		t2.start();
	}

}

6.4、テストクラスを実行し、結果は以下の通りである.
Tue Mar 20 12:27:15 CST 2012 Thread-0, method1
Tue Mar 20 12:27:20 CST 2012 Thread-1, method2

 
 
ノット:同期メソッドが1つのオブジェクトをロックオブジェクトとして共用している場合、これらの同期メソッドのいずれかが実行中である場合、残りの同期メソッドは呼び出されません.例えば、同期方法aと同期方法bのロック対象がいずれも現在のクラスオブジェクトである場合、スレッドAが同期方法aを実行する場合、スレッドBは同期方法aも同期方法bも実行できない.
 
まとめ:
1、非静的方法はsynchronizedキーワードで修飾され、同期コードブロック内のsynchronized(this)に等価であり、すなわち現在のオブジェクトをロック対象とする.
2、静的方法はsynchronizedキーワードで修飾され、同期コードブロック内のsynchronized(現在のクラス.class)に等価であり、すなわち現在のクラスclassオブジェクトをロック対象とする.
3、1つのオブジェクトは、同時に1つのロックを追加することしかできません.1つのスレッドが1つのオブジェクトにロックを追加した後、残りのスレッドはそのオブジェクトにロックを追加することはできません.ロックが除去されるのを待ってから、ロックを追加することができます.
4、1つのスレッドが同期コードブロックまたは同期方法に実行される場合、中のコードを実行するには、対応するロックオブジェクトをロックしなければならない.
5.1つのスレッドが同期コードブロックまたは同期方法を実行すると、対応するロック対象のロックが自動的に除去される.
6、ロック対象選択ポリシー:ロック対象として共有リソースを選択し、ロック対象として共有リソースが不便な場合、実際のビジネスシーンに基づいてthis、class、第三者オブジェクトを選択し、ロック対象とすることができる.
 
http://huangqiqing123.iteye.com/admin/blogs/1458542