Java同時プログラミングの内蔵ロック(synchronized)を詳しく説明します。


概要
synchronizedはJDK 5.0の初期バージョンではヘビー級ロックであり、効率は低いが、JDK 6.0からは、JDKはキーワードsynchronizedに大量の最適化を行っており、例えば、バイアスロック、軽量級錠など、効率を大幅に向上させている。
synchronizedの役割はスレッド間の同期を実現することであり、複数のスレッドが共有コードエリアにアクセスする必要がある場合、共有コードエリアをロックし、毎回共有コードエリアにしかスレッドがアクセスできないため、スレッド間の安全性を保証する。
明示的にロックやロック解除が行われていないため、隠蔽ロックとも呼ばれ、内蔵ロック、モニタロックとも呼ばれます。
以下の例では、synchronizedを使用しない場合、複数のスレッドが共有コード領域にアクセスすると、予想外の結果が生じる可能性がある。

public class Apple implements Runnable {
 private int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 public void eatApple(){
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
 }

 public static void main(String[] args) {
  Apple apple = new Apple();
  Thread t1 = new Thread(apple, "  ");
  Thread t2 = new Thread(apple, "  ");
  Thread t3 = new Thread(apple, "  ");
  Thread t4 = new Thread(apple, "  ");
  Thread t5 = new Thread(apple, "  ");
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }
}
次のような結果が出力される可能性があります。
強ちゃんはリンゴを一つ食べましたが、まだ三つのリンゴがあります。
クロさんはリンゴを一つ食べましたが、まだ三つのリンゴがあります。
明日はリンゴを一つ食べましたが、まだ二つのリンゴがあります。
小さい花はリンゴを一つ食べましたが、まだリンゴが一つ残っています。
紅ちゃんはリンゴを一つ食べましたが、まだ0個のリンゴがあります。
出力結果の異常の原因は、AtAppleメソッドでは原子ではなく、Aスレッドがapple Countの割り当てを完了したとき、まだ出力されていません。Bスレッドはapple Countの最新の値を取得し、割り当て作業を完了したら、AとBは同時に出力されます。A,Bスレッドはそれぞれ小黒,小強に対応している)
もしAtAppleの方法を変えたら、スレッドの安全問題はありませんか?

public void eatApple(){
	System.out.println(Thread.currentThread().getName() + "      ,  " + --appleCount + "   ");
}
まだあります。原子操作ではないので、apple Countは別の表記でapple Count=apple Count-1を表してもいいです。やはり以上の異常出力結果が出る可能性があります。
synchronizedの使用
synchronizedは同期方法と同期コードブロックの2つの使い方に分けられています。各スレッドが同期方法または同期コードブロック領域にアクセスする時、まず対象のロックを取得し、ロックを奪うスレッドは継続して実行できます。ロックを奪わないスレッドはブロックされ、ロックが完了するまで待ちます。
1.同期コードブロック
ロックの対象はobjectです。

public class Apple implements Runnable {
 private int appleCount = 5;
 private Object object = new Object();

 @Override
 public void run() {
  eatApple();
 }

 public void eatApple(){
	//     ,       object
  synchronized (object) {
   appleCount--;
   System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
  }
 }

  //...  main  
}
2.同期方法、普通の方法を修飾する
ロックの対象は現在のクラスのインスタンスオブジェクトです。

public class Apple implements Runnable {
 private int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 public synchronized void eatApple() {
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
 }

 //...  main  
}
以下の同期コードブロックの書き方と同じです。

public void eatApple() {
	synchronized (this) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
	}
}
3.同期方法、静的方法を修飾する
ロックの対象は現在のクラスのクラスのクラスです。

public class Apple implements Runnable {
 private static int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 public synchronized static void eatApple() {
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
 }

 //...  main  
}
以下の同期コードブロックの書き方と同じです。

public static void eatApple() {
	synchronized (Apple.class) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
	}
}
4.同期方法と同期コードブロックの違い
a.同期方法のロックの対象は現在のクラスのインスタンスオブジェクトまたはクラスのクラスクラスクラスクラスのクラスクラスクラスクラスのクラスのクラスのクラスオブジェクトであり、同期コードブロックのロックの対象は任意のオブジェクトであってもよい。
b.同期方法は、synchronized修飾方法を使用し、同期コードブロックは、synchronizedを用いて共有コード領域を修飾する。同期コードブロックは同期方法に比べて粒度がより細く、ロック領域がより小さく、一般的にロック範囲が小さいほど効率が高い。下記の状況は明らかに同期コードブロックがより適用されます。

public static void eatApple() {
	//          1
	//...
	synchronized (Apple.class) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
	}
	//          2
	//...
}
内蔵ロックの可搬性
内蔵ロックの再入力性とは、あるスレッドがすでに持っているロックを取得しようとしたときに、常に成功します。以下のとおりです

public static void eatApple() {
	synchronized (Apple.class) {
		synchronized (Apple.class) {
			synchronized (Apple.class) {
				appleCount--;
				System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
			}
		}
	}
}
もしロックが重入可能ではないなら、もしあるスレッドがこのロックを持っていたら、またそのロックを持っているスレッドがロックを解除するのを待つ必要があります。
synchronizedは継承されますか?
synchronizedは継承できません。サブクラスで書き換えた方法に同期が必要な場合は、手動でsynchronizedキーワードを追加する必要があります。

public class AppleParent {
 public synchronized void eatApple(){

 }
}

public class Apple extends AppleParent implements Runnable {
 private int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 @Override
 public void eatApple() {
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "      ,  " + appleCount + "   ");
 }

 //...  main  
}
内蔵ロックによる待ち時間と目覚まし
内蔵ロックによる待ち時間と起動はObjectクラスのwait()とnotify()またはnotifyAll()を使用して実現される。これらの方法の呼び出しは、対応するロックが既に保有されているため、同期方法または同期コードブロック内でのみ起動される。対応するロックが取得されていない場合は、呼び出しはIllegel MonitorsteExceptionに異常があります。関連するいくつかの方法を紹介します。
wait():現在スレッドを無期限に待機させ、他のスレッドがnotify()またはnotifyAll()を起動するまで待つ。
wait(long timeout):タイムアウト時間を指定します。タイムアウト時間が過ぎると、スレッドが自動的に起動されます。スレッドは、タイムアウト時間前にnotify()またはnotifyAll()によって呼び覚まされてもよい。注意、wait(0)はwait()を呼び出すのと同じです。
wait(long timeout、int nanos):wait(long timeout)に似ています。主にwait(long timeout、int nanos)がより高い精度を提供しています。
notify():同じロックオブジェクト上で待つスレッドをランダムに起動します。
notifyAll():同じロックオブジェクトで待つスレッドを全て呼び覚まします。
起動待ちの簡単な例:

public class Apple {
 //    
 private int appleCount = 0;

 /**
  *    
  */
 public synchronized void getApple() {
  try {
   while (appleCount != 0) {
    wait();
   }
  } catch (InterruptedException ex) {
   ex.printStackTrace();
  }

  System.out.println(Thread.currentThread().getName() + "  5   ");
  appleCount = 5;
  notify();
 }

 /**
  *    
  */
 public synchronized void eatApple() {
  try {
   while (appleCount == 0) {
    wait();
   }
  } catch (InterruptedException ex) {
   ex.printStackTrace();
  }

  System.out.println(Thread.currentThread().getName() + "  1   ");
  appleCount--;
  notify();
 }
}

/**
 *    ,   
 */
public class Producer extends Thread{
 private Apple apple;

 public Producer(Apple apple, String name){
  super(name);
  this.apple = apple;
 }

 @Override
 public void run(){
  while (true)
  apple.getApple();
 }
}

/**
 *    ,   
 */
public class Consumer extends Thread{
 private Apple apple;

 public Consumer(Apple apple, String name){
  super(name);
  this.apple = apple;
 }

 @Override
 public void run(){
  while (true)
  apple.eatApple();
 }
}

public class Demo {
 public static void main(String[] args) {
  Apple apple = new Apple();
  Producer producer = new Producer(apple,"  ");
  Consumer consumer = new Consumer(apple, "  ");
  producer.start();
  consumer.start();
 }
}
出力結果:
明さんはりんごを五つ買いました。
紅ちゃんはリンゴを一つ食べました。
紅ちゃんはリンゴを一つ食べました。
紅ちゃんはリンゴを一つ食べました。
紅ちゃんはリンゴを一つ食べました。
紅ちゃんはリンゴを一つ食べました。
明さんはりんごを五つ買いました。
紅ちゃんはリンゴを一つ食べました。
    ......
ここで、Java併発プログラミングの内蔵ロックについての記事を紹介します。Java関連のロック内容は以前の文章を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。