synchronizedについて


synchronizedのJAVAにおける役割は、コードブロックへのスレッドの反発アクセスを保証するためのスレッドの同期メカニズムであり、すなわち、複数のスレッドが同期して実行される場合、同じ時点で、1つのスレッドだけがコードブロックに入ることができる.
synchronizedにはsynchronizedメソッドとsynchronizedブロックの2つの使用方法があります.ネット上ではこの2つの使い方について説明することが多いが、ロックの違いを説明する文章も少なくない.実は結局、synchronizedが具体的にロックされているのが対象であることを明らかにしてこそ、synchronizedが正しく使われているかどうかを判断することができる.
実は根本的にsynchronizedは,スレッドがオブジェクトを持っているかどうかを決定した後,スレッドが対応するコードブロックを実行できることを確認する.だから原理を明らかにした後、二つの使い方に根本的な違いはないことが分かった.
1.synchronizedメソッド
synchronizedメソッドは,メソッドの前にsynchronizedキーワードを付けて修飾する.実はこの使い方には一定の迷いがあり、最初にスレッドに触れたとき、synchronizedキーワードが付けられると、スレッドがどのように使用されても、1つのスレッドだけがある時点でsynchronizedメソッドを実行できると感じがちです.しかし、実際にはそうではありません.次のコードクリップを見てください.
public class Sync1 implements Runnable {
	@Override
	public void run() {
		syncMethodPrint();
	}

	public synchronized void syncMethodPrint() {
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(10);//                       ,           
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "   ");
		}
	}

	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		Sync1 s2 = new Sync1();

		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s2, "  2");

		t1.start();
		t2.start();
	}

}

コードから見ると、2つのスレッドはsyncMethodPrintメソッドを呼び出し、それぞれ5回印刷します.syncMethodPrintにはロックがあるため、最初にこのメソッドに入ったスレッドはまず5回印刷され、別のメソッドはt 1スレッドが最初に実行されるため、一般的には実行結果は次のように推定されます.
  1   
  1   
  1   
  1   
  1   
  2   
  2   
  2   
  2   
  2   

理論的にはスレッド2がスレッド1より先に実行される確率もあるようだ.しかし、いずれにしても、2つのスレッドが交互に印刷されることはない(syncMethodPrintメソッドブロックに反発する)が、実際には、実行結果は以下のようになる.
  1   
  2   
  1   
  2   
  2   
  1   
  2   
  1   
  2   
  1   

syncMethodPrintメソッドを修飾するsynchronizedは、予想される結果に従って実行されず、2つのスレッドが交互に印刷されたようです.
この状況の原因は、synchronizedメソッドの本質から言えば、synchronizedメソッドの本質は、synchronized(this)と同等であり、このメソッドがロックされているのは、メソッドを呼び出すオブジェクトである.一方、例では、2つのスレッドは異なるSync 1オブジェクトs 1およびs 2から来ているため、2つのスレッドはそれぞれ異なるオブジェクトs 1およびs 2をロックしており、反発するsyncMethodPrintメソッドへのアクセスは自然に実現できない.
次のように変更します.
	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		
		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s1, "  2");

		t1.start();
		t2.start();
	}

次の結果が得られます.
  1   
  1   
  1   
  1   
  1   
  2   
  2   
  2   
  2   
  2   

synchronizedは同じオブジェクトs 1をロックするため、t 1とt 2の2つのスレッドは反発的なアクセスを実現する.
2.synchronizedブロック.
synchronized法について上述した議論から,synchronizedがコードブロックへの異なるスレッドの反発アクセスを保証するためにオブジェクトをロックしていることが明らかになった.synchronizedブロックの概念を理解するには、次のコードを参照してください.
public class Sync1 implements Runnable {

	@Override
	public void run() {
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(10);//                        ,           
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "   ");
			}
		}
	}

	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		Sync1 s2 = new Sync1();

		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s2, "  2");

		t1.start();
		t2.start();
	}
}

実はこのような方法に変えて、前のsynchronizedの方法と本質的に同じです.実行すると、2つのスレッドは並列に実行されます.
  2   
  1   
  2   
  1   
  2   
  1   
  1   
  2   
  1   
  2   

理由は、synchronizedがロックしたオブジェクトがthisであるため、2つのスレッドがそれぞれ2つの異なるオブジェクトs 1とs 2(異なるthis)にインスタンス化されるため、上記の結果が得られる.
前の例と同じように変更すると、次のようになります.
	public static void main(String args[]) {
		Sync1 s1 = new Sync1();

		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s1, "  2");

		t1.start();
		t2.start();
	}

2つのスレッドは、予想されるように反発して実行されます.
  1   
  1   
  1   
  1   
  1   
  2   
  2   
  2   
  2   
  2   

問題の本質を明らかにした後、より多くの実験を続けると、次のような明確な結果が得られます.
public class Sync1 implements Runnable {

	private Object lock = null;

	public void setLock(Object lock) {
		this.lock = lock;
	}

	@Override
	public void run() {
		synchronized (lock) {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(10);//                        ,           
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "   ");
			}
		}
	}

	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		Sync1 s2 = new Sync1();

		Object lock = new Object();
		s1.setLock(lock);
		s2.setLock(lock);

		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s2, "  2");

		t1.start();
		t2.start();
	}
}

この例では、ロックとしてオブジェクトlockを使用します.何の結果が得られますか?2つのスレッドオブジェクトが同じlockオブジェクトを参照していることがわかりました.反発するsynchronizedブロックへのアクセスが可能ですか?答えは肯定的です.
  1   
  1   
  1   
  1   
  1   
  2   
  2   
  2   
  2   
  2   

2つのスレッドのsynchronizedは同じオブジェクトをロックしているため、あるスレッドがオブジェクトをロックしている場合、別のスレッドはオブジェクトが解放されてから実行を続行することができます.
次のように変更します.
	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		Sync1 s2 = new Sync1();

		Object lock = new Object();
		Object lock2 = new Object();
		
		s1.setLock(lock);
		s2.setLock(lock2);

		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s2, "  2");

		t1.start();
		t2.start();
	}

2つのスレッドの反発アクセスは失敗しました.ロックされているのは2つの異なるオブジェクトなので、結果は次のようになります.
  1   
  2   
  1   
  2   
  1   
  2   
  1   
  2   
  1   
  2   

では、synchronizedロックのオブジェクトとして他のオブジェクトを使用できますか?例えばクラス?それともstatic変数?例:
public class Sync1 implements Runnable {

	@Override
	public void run() {
		synchronized (Sync1.class) {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(10);//                        ,           
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "   ");
			}
		}
	}

	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		Sync1 s2 = new Sync1();
		
		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s2, "  2");

		t1.start();
		t2.start();
	}
}

実験により,反発アクセスの結果が得られた.
  1   
  1   
  1   
  1   
  1   
  2   
  2   
  2   
  2   
  2   

これは、2つのスレッドが同じclassオブジェクトをロックしているため、予想外ではありません.結局、上記の例ではSync 1.classは一度だけJVMにロードされます.
staticオブジェクトをロックすると、反発アクセスの結果が得られることは間違いありません.
public class Sync1 implements Runnable {
	
	static Object lock = new Object();

	@Override
	public void run() {
		synchronized (lock) {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(10);//                        ,           
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "   ");
			}
		}
	}

	public static void main(String args[]) {
		Sync1 s1 = new Sync1();
		Sync1 s2 = new Sync1();
		
		Thread t1 = new Thread(s1, "  1");
		Thread t2 = new Thread(s2, "  2");

		t1.start();
		t2.start();
	}
}

結果は次のとおりです.
  1   
  1   
  1   
  1   
  1   
  2   
  2   
  2   
  2   
  2   

結論:
JAVAでsynchronizedはスレッド反発アクセスにおいて非常に重要な役割を果たすが,このキーワードを正しく使用する根本は,そのロックされたオブジェクトが何であるかを明らかにすることであり,2つのスレッドが異なるオブジェクトをロックすると反発アクセスが失効し,synchronizedは本来備えるべき役割を失う.