1
1

Java Gold 例題 同期制御

Last updated at Posted at 2024-08-12
package threadTest;

public class SynchronizeTest {
	public static void main(String[] args) {
		Market market = new Market();
		ThreadFirst tf = new ThreadFirst(market);
		ThreadSecond ts = new ThreadSecond(market);
		tf.start();
		ts.start();
	}
}

class ThreadFirst extends Thread {
	private Market market;
	public ThreadFirst(Market m) {
		market = m;
	}
	public void run() {
		for(int i = 0; i < 4; i++) {
			int num = (int)(Math.random() * 10 + 1);
			market.wholesale(num);
		}
	}
}

class ThreadSecond extends Thread {
	private Market market;
	public ThreadSecond(Market m) {
		market = m;
	}
	public void run() {
		for(int i = 0; i < 4; i++) {
			int num = (int)(Math.random() * 4 + 1);
			market.sell(num);
		}
	}
}

class Market {
	private int productStock = 0;
	synchronized void wholesale(int num) {
		while(productStock != 0) {
			try { 
				wait();
			} catch(InterruptedException e) {
			}
		}
		notifyAll();
		productStock += num;
		System.out.println("入荷しました! 商品の在庫 : " + productStock);
	}
	
	synchronized void sell(int num) {
		while(productStock == 0) {
			try {
				wait();
			} catch(InterruptedException e) {
			}
		}
		notifyAll();
		productStock -= num;
		
		if(productStock <= 0) {
			num += productStock;
			productStock = 0;
			System.out.println("販売数 : " + num);
			System.out.println("売り切れ! ");
		} else {
			System.out.println("販売数 : " + num);
		}
	}
}

このコードの実行結果として当てはまらないものを2つ答えてください。
A
入荷しました! 商品の在庫 : 1
販売数 : 1
売り切れ!
入荷しました! 商品の在庫 : 1
販売数 : 1
売り切れ!
入荷しました! 商品の在庫 : 2
販売数 : 1

B
入荷しました! 商品の在庫 : 1
販売数 : 1
売り切れ!
入荷しました! 商品の在庫 : 1
販売数 : 0
販売数 : 1
売り切れ!
入荷しました! 商品の在庫 : 4

C
入荷しました! 商品の在庫 : 4
販売数 : 1
販売数 : 1
販売数 : 1

D
入荷しました! 商品の在庫 : 4
販売数 : 1
販売数 : 3
売り切れ!
入荷しました! 商品の在庫 : 1
販売数 : 1
売り切れ!
入荷しました! 商品の在庫 : 4

E
販売数 : 3
販売数 : 4
販売数 : 3
入荷しました! 商品の在庫 : 1
入荷しました! 商品の在庫 : 2

B, E
Bの結果では販売数0が含まれていますが、販売数は1~4の整数になります。

int num = (int)(Math.random() * 4 + 1);

Math.random()では0以上1未満のdouble値が生成されます。
その結果に+1したものをint型へ変換しています。
これにより1から4の整数をランダムに取得することができます。

例えばrandom()の結果が0.9111...`だった場合、0.9111... * 4 = 3.6444...となります。
その結果に+1するので4.6444...となります。

(int) 4.6444
intへキャストされると、小数点切り捨てにより4となります。

よってint num = (int)(Math.random() * 4 + 1)
変数numには1~4の整数が代入されます。

Eの結果では最初の出力が販売になっています。
productStockが0の状態でsell()が呼ばれた時、wait()メソッドで待機状態になります。

class Market {
	private int productStock = 0;
    synchronized void sell(int num) {
    while(productStock == 0) {
        try {
            wait();
        } catch(InterruptedException e) {
        }
    }
    notifyAll();
    productStock -= num;

ThreadFirstwholesaleメソッドが呼び出されることで、productStockに1~10の整数が代入され、notifyAll()によりInterruputedExceptionをcatchし、ThreadSecondの処理が再開されます。

synchronized void wholesale(int num) {
  	while(productStock != 0) {
  		try { 
  			wait();
  		} catch(InterruptedException e) {
  		}
  	}
  	notifyAll();
  	productStock += num;

よって、最初の処理は入荷となります。

補足

このコードは、2つのスレッドが互いに競合しないように、商品在庫を同期化して管理する方法を示しています。synchronizedwait(), notifyAll() を使って、複数スレッドが協調して在庫の入荷と販売を行うシミュレーションをしています。

SynchronizeTestクラス

public class SynchronizeTest {
    public static void main(String[] args) {
        Market market = new Market();
        ThreadFirst tf = new ThreadFirst(market);
        ThreadSecond ts = new ThreadSecond(market);
        tf.start();
        ts.start();
    }
}

ThreadFirstThreadSecond の作成と開始。
2つのスレッド (tf と ts) を作成し、それぞれ Market オブジェクトを共有します。
tf は入荷、ts は販売を担当し、それぞれのスレッドが start() メソッドで並行して動作します。

ThreadFirstクラス

class ThreadFirst extends Thread {
    private Market market;
    public ThreadFirst(Market m) {
        market = m;
    }
    public void run() {
        for(int i = 0; i < 4; i++) {
            int num = (int)(Math.random() * 10 + 1);
            market.wholesale(num);
        }
    }
}

run() メソッド: スレッドがstart()する際に呼び出されるコードです。
Math.random() でランダムな数の商品の入荷処理を3回まで行います。
Marketクラスに定義された在庫の入荷を意味するwholesale メソッドを呼び出します。

synchronized void wholesale(int num) {
        while(productStock != 0) {
            try { 
                wait();
            } catch(InterruptedException e) {
            }
        }
        notifyAll();
        productStock += num;
        System.out.println("入荷しました! 商品の在庫 : " + productStock);
    }

synchronized で同期化されており、複数のスレッドが同時にこのメソッドにアクセスできないようにします。
入荷は在庫がゼロのときにのみ行われ、入荷が終わると他のスレッドに通知します (notifyAll() を呼び出します)。

ThreadSecondクラス

class ThreadSecond extends Thread {
    private Market market;
    public ThreadSecond(Market m) {
        market = m;
    }
    public void run() {
        for(int i = 0; i < 4; i++) {
            int num = (int)(Math.random() * 4 + 1);
            market.sell(num);
        }
    }
}

ランダムな数の商品の販売処理を3回まで行います。
Marketクラスに定義された商品を販売するメソッドsellを呼び出します。

synchronized void sell(int num) {
        while(productStock == 0) {
            try {
                wait();
            } catch(InterruptedException e) {
            }
        }
        notifyAll();
        productStock -= num;
        
        if(productStock <= 0) {
            num += productStock;
            productStock = 0;
            System.out.println("販売数 : " + num);
            System.out.println("売り切れ! ");
        } else {
            System.out.println("販売数 : " + num);
        }
    }

販売は在庫がある場合にのみ行われ、販売後は在庫数を減らします。
在庫が0以下になると、「売り切れ」を表示します。また、販売が終わると他のスレッドに通知します (notifyAll() を呼び出します)。

Marketクラス

class Market {
    private int productStock = 0;

    synchronized void wholesale(int num) {
        while(productStock != 0) {
            try { 
                wait();
            } catch(InterruptedException e) {
            }
        }
        notifyAll();
        productStock += num;
        System.out.println("入荷しました! 商品の在庫 : " + productStock);
    }

    synchronized void sell(int num) {
        while(productStock == 0) {
            try {
                wait();
            } catch(InterruptedException e) {
            }
        }
        notifyAll();
        productStock -= num;
        
        if(productStock <= 0) {
            num += productStock;
            productStock = 0;
            System.out.println("販売数 : " + num);
            System.out.println("売り切れ! ");
        } else {
            System.out.println("販売数 : " + num);
        }
    }
}

一行目のメンバproductStockの数によって、スレッドを制御します。

在庫が0ではない時(在庫が1以上あるとき)wholesale()メソッドのwhile(productStock != 0)trueとなり、wait()されます。sell()メソッドのnotifyAll()によって処理を再開します。
初期状態ではprivate int productStock = 0;となっているため、在庫は0になります。
よって

notifyAll();
productStock += num;
System.out.println("入荷しました! 商品の在庫 : " + productStock);

が必ず最初に実行されます。
その後、sell()メソッドにより在庫が0になるまで待機状態となります。
在庫が0の時、sell()メソッドのwhile(productStock == 0)trueとなります。
初期状態では在庫0なので、割り込みが発生するまで待機状態になります。
商品が入荷されると処理を再開します。
在庫から販売数を引きます。
もし、販売数が在庫数以上の場合。
productStock -= num;
在庫数が0以下、if(productStock <= 0)trueになります。
num += productStok
productStock = 0;
とすることで在庫数を0に
販売数を販売前の在庫数に設定します。

例えば、在庫数1で販売数9の場合
productStock -= num;
1 -= 9
num = 9
productStock = -8

num += productStock
9 += -8
productStock = -8
num = 9 - 8 = 1
productStock = 0

在庫1のときに9個販売したあと、在庫は0、販売数は1になります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1