Javaマルチスレッド-(二)スレッド同期synchronizedとvolatile


一、デッドロックをもたらす可能性のあるSynchrinized
 
スレッド同期の問題をコードで示します.
/**
 * 
 */
package com.wsheng.thread.synchronize;

/**
 * @author Wang Sheng(Josh)
 *
 */
public class ThreadSynchronized {

	
	public static void main(String[] args) {
		final Outputer output = new Outputer();
		new Thread() {
			public void run() {
				// output.output("lisi");
				output.output("lisilisilisilisilisilisilisilisilisilisi");
			};
		}.start();
		
		new Thread(){
			public void run() {
				output.output("zhangshan");
			};
		}.start();

	}

}

class Outputer {
	public void output(String name) {
		
		//  name , name 
		for (int i = 0; i < name.length(); i++) {
			System.out.print(name.charAt(i));
		}
	}
}

1回の実行結果は次のとおりです.
lisilisilisilisilisilisilisiliszilhisailisingshan
 
明らかに出力された文字列が乱され、私たちが望んでいる出力結果はzhangsanlisiであり、これがスレッド同期の問題であり、outputメソッドが1つのスレッドによって完全に実行された後、次のスレッドに切り替えることを望んでいる.Javaではsynchronizedを使用して、1つのコードがマルチスレッド実行時に反発することを保証し、2つの使い方がある.
 
        1. synchronizedを使用して、反発が必要なコードを含め、ロックをかけます.
synchronized (this) {  
    for(int i = 0; i < name.length(); i++) {  
        System.out.print(name.charAt(i));  
    }  
}

このロックはスレッド間の共有オブジェクトでなければなりません.次のコードのように意味がありません.
Object lock = new Object();  
synchronized (lock) {  
    for (int i = 0; i < name.length(); i++) {  
        System.out.print(name.charAt(i));  
    }  
}

outputメソッドに入るたびに新しいlockが作成されます.このロックは明らかに各スレッドが作成され、意味がありません.
 
   2. synchronizedを反発が必要な方法に追加します.
    public synchronized void output(String name) {  
        for(int i = 0; i < name.length(); i++) {  
            System.out.print(name.charAt(i));  
        }  
    }  

この方式はthisでメソッド全体をロックするコードブロックに相当し,synchronizedで静的メソッドに加算すると,××××.classはメソッド全体のコードブロックをロックします.synchronizedを使用すると、場合によってはデッドロックになります.デッドロックの問題は後で説明します.
 
各ロックオブジェクトには2つのキューがあり、1つは準備キュー、1つはブロックキューであり、準備キューはロックを取得するスレッドを格納し、ブロックキューはブロックされたスレッドを格納し、1つのスレッドが起動されると、準備キューに入り、CPUのスケジューリングを待つ.逆に、1つのスレッドがwaitされると、ブロックキューに入り、次の起動を待つ.これはスレッド間の通信に関して、次のブログで説明します.我々の例を見ると、第1のスレッドが出力メソッドを実行すると、同期ロックが得られ、出力メソッドが実行され、ちょうどこのとき第2のスレッドも出力メソッドを実行するが、同期ロックが解放されていないことが判明し、第2のスレッドは準備キューに入り、ロックが解放されるのを待つ.1つのスレッドが反発コードを実行する手順は、次のとおりです.
        1. 同期ロックを取得します.
        2. ワークメモリを空にします.
        3. メインメモリからオブジェクトのコピーをワークメモリにコピーします.
        4. 実行コード(計算や出力など);
        5. メインメモリデータをリフレッシュします.
        6. 同期ロックを解除します.
したがってsynchronizedはマルチスレッドのメモリ可視性を保証するだけでなく、マルチスレッドの同時秩序性を保証するスレッドのランダム実行性の問題も解決する.
    
二、相対変態のVolatile:
volatileは2番目のJavaマルチスレッド同期の手段であり、JLSによると、1つの変数をvolatileで修飾することができ、この場合メモリモデルはすべてのスレッドが一致する変数値を見ることができることを確保し、コードを見てみましょう.
         
/**
 * 
 */
package com.wsheng.thread.synchronize;

/**
 * @author Wang Sheng(Josh)
 *
 */
public class VolatileTest {
	
	static int i = 0, j = 0;  
    static void one() {  
    	while (i <= 100000) {
    		i++;  
            j++; 
    	}
         
    }  
    static void two() {  
        System.out.println("i=" + i + " j=" + j);  
    }  

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// final VolatileTest test = new VolatileTest();
		
		new Thread() {
			public void run() {
				VolatileTest.one();
			};
		}.start();
		
		new Thread() {
			public void run() {
				VolatileTest.two();
			};
		}.start();

	}

}

 
可能な実行結果:
i=72930 j=73145
i=24357 j=24511
i=17586 j=17732
i=19357 j=19545
 
一部のスレッドはoneメソッドを実行し、他のスレッドはtwoメソッドを実行し、twoメソッドは異なるiとjの値を印刷する可能性があり、前に分析したスレッド実行プロセスに従って分析します.
 
        1. 変数iをプライマリメモリからワークメモリにコピーする.
        2. iの値を変更する.
        3. メインメモリデータをリフレッシュします.
        4. 変数jをメインメモリからワークメモリにコピーする.
        5. jの値を変更する.
        6. メインメモリデータをリフレッシュします.
 
このときtwoメソッドを実行するスレッドは、ホストiの元の値を先に読み出し、jが変更された値を読み出し、プログラムの出力が予想された結果ではないため、共有変数の前にvolatileを加えることができる.
 
/**
 * 
 */
package com.wsheng.thread.synchronize;

/**
 * @author Wang Sheng(Josh)
 *
 */
public class VolatileTest2 {
	
	static volatile int i = 0, j = 0;  
    static void one() {  
    	while (i <= 100000) {
    		i++;  
            j++; 
    	}
         
    }  
    static void two() {  
        System.out.println("i=" + i + " j=" + j);  
    }  

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// final VolatileTest test = new VolatileTest();
		
		new Thread() {
			public void run() {
				VolatileTest2.one();
			};
		}.start();
		
		new Thread() {
			public void run() {
				VolatileTest2.two();
			};
		}.start();

	}

}

可能な出力結果:
i=19944 j=20094
i=79462 j=79694
 
volatileを加えると、共有変数iとjの変更をプライマリメモリに直接応答することができ、iとjの値が一致することを保証することができるが、twoメソッドを実行するスレッドがiとjでどの程度実行されたかを保証することはできないので、volatileはメモリの可視性を保証することができ、同時秩序性を保証することはできないので、上記の出力結果では、iとjの値は異なる.