Difference Between wait and notify in Java - Java 147

Difference Between wait and notify in Java – Java 147

Difference Between wait and notify in Java

在多執行緒的環境中,多個執行緒可能會嘗試修改同一資源,不正確地管理執行緒會導致一致性的問題,使用 synchronized 來保護協調程式碼運行,調用 wait 方法時,調用執行緒停止執行,直到 notify 或 notifyAll 方法被其他某個執行緒調用, Difference Between notify and wait in Java ,提供這兩種方法的應用, 本篇增加了範例,並透過單元測試來驗證產出結果。

檔案目錄

./
   +- src
       +- test
       |   +- org
       |       +- ruoxue
       |           +- java_147
       |               +- multithreading
       |                   +- thread
       |                       +- DifferenceWaitNotifyTest.java   

單元測試

Difference Between wait and notify 提供執行緒暫停、喚醒、拋出例外等操作。

worker

Difference Between wait and notify 建立 1 個物件,提供儲存跟讀取 2 個方法,當儲存執行緒調用 put 方法耗時 3 秒,完成任務時,會喚醒另一條讀取執行緒,因調用 take 方法而被暫停的執行緒,建立 2 條執行緒,分別調用 1 個物件儲存跟讀取的方法。

	protected class Worker {

		private volatile boolean done = false;

		public Worker() {
		}

		public synchronized void put() throws InterruptedException {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("T[" + Thread.currentThread().getId() + "] put finished");
			done = true;
			notify();
		}

		public synchronized boolean take() throws InterruptedException {
			while (!done) {
				System.out.println("T[" + Thread.currentThread().getId() + "] take waiting");
				wait();
			}
			return done;
		}
	}

	@Test
	public void worker() {
		Worker worker = new Worker();
		Thread threadA = new Thread(() -> {
			String id = "A";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				worker.put();
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " finished");
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		Thread threadB = new Thread(() -> {
			String id = "B";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				boolean done = worker.take();
				System.out.println(
						"T[" + Thread.currentThread().getId() + "] worker: " + id + " finished, result: " + done);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		threadB.start();
		threadA.start();

		try {
			threadA.join();
			threadB.join();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}
T[12] worker: B ready
T[11] worker: A ready
T[12] take waiting
T[11] put finished
T[11] worker: A finished
T[12] worker: B finished, result: true

interruptWorker

Difference Between wait and notify 建立 1 個物件,提供儲存跟讀取 2 個方法,當儲存執行緒,調用 put 方法耗時 3 秒,完成任務時,沒有喚醒另一條讀取執行緒,因調用 take 方法而被暫停的執行緒,建立 2 條執行緒,分別調用 1 個物件儲存跟讀取的方法,主執行緒等待 2 秒後,調用 interrupt 中斷讀取執行緒。

	protected class InterruptWorker {

		private volatile boolean done = false;

		public InterruptWorker() {
		}

		public synchronized void put() throws InterruptedException {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("T[" + Thread.currentThread().getId() + "] put finished");
			done = true;
			// notify();
		}

		public synchronized boolean take() throws InterruptedException {
			while (!done) {
				System.out.println("T[" + Thread.currentThread().getId() + "] take waiting");
				wait();
			}
			return done;
		}
	}

	@Test
	public void interruptWorker() {
		InterruptWorker worker = new InterruptWorker();
		Thread threadA = new Thread(() -> {
			String id = "A";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				worker.put();
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " finished");
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		Thread threadB = new Thread(() -> {
			String id = "B";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				boolean done = worker.take();
				System.out.println(
						"T[" + Thread.currentThread().getId() + "] worker: " + id + " finished, result: " + done);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		threadB.start();
		threadA.start();

		try {
			threadA.join();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}

		long startTime = System.currentTimeMillis();
		while (true) {
			if (System.currentTimeMillis() - startTime > 1000) {
				threadB.interrupt();
				break;
			}
		}
	}
T[12] worker: B ready
T[12] take waiting
T[11] worker: A ready
T[11] put finished
T[11] worker: A finished
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at org.ruoxue.java_147.multithreading.DifferenceWaitNotifyTest$InterruptWorker.take(DifferenceWaitNotifyTest.java:92)

illegalMonitorWorker

Difference Between wait and notify 建立 1 個物件,提供儲存跟讀取 2 個方法,當儲存執行緒使用另一個物件鎖與 notify 為不同的鎖時,會拋出例外,結束任務。

	protected class IllegalMonitorWorker {

		private volatile boolean done = false;
		private final Object lock = new Object();

		public IllegalMonitorWorker() {
		}

		public void put() throws InterruptedException {
			synchronized (lock) {
				TimeUnit.SECONDS.sleep(3);
				System.out.println("T[" + Thread.currentThread().getId() + "] put finished");
				done = true;
				notify();
			}
		}

		public boolean take() throws InterruptedException {
			synchronized (lock) {
				while (!done) {
					System.out.println("T[" + Thread.currentThread().getId() + "] take waiting");
					wait();
				}
			}
			return done;
		}
	}

	@Test
	public void illegalMonitorWorker() {
		IllegalMonitorWorker worker = new IllegalMonitorWorker();
		Thread threadA = new Thread(() -> {
			String id = "A";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				worker.put();
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " finished");
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		Thread threadB = new Thread(() -> {
			String id = "B";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				boolean done = worker.take();
				System.out.println(
						"T[" + Thread.currentThread().getId() + "] worker: " + id + " finished, result: " + done);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		threadB.start();
		threadA.start();

		try {
			threadB.join();
			threadA.join();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}
T[12] worker: B ready
T[11] worker: A ready
Exception in thread "Thread-1" T[12] take waiting
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at org.ruoxue.java_147.multithreading.DifferenceWaitNotifyTest$IllegalMonitorWorker.take(DifferenceWaitNotifyTest.java:166)
	at org.ruoxue.java_147.multithreading.DifferenceWaitNotifyTest.lambda$5(DifferenceWaitNotifyTest.java:191)
	at java.lang.Thread.run(Thread.java:748)
T[11] put finished
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at org.ruoxue.java_147.multithreading.DifferenceWaitNotifyTest$IllegalMonitorWorker.put(DifferenceWaitNotifyTest.java:157)
	at org.ruoxue.java_147.multithreading.DifferenceWaitNotifyTest.lambda$4(DifferenceWaitNotifyTest.java:180)
	at java.lang.Thread.run(Thread.java:748)

DifferenceWaitNotifyTest.java

Difference Between notify and wait 新增單元測試,驗證是否符合預期。

package org.ruoxue.java_147.synchronization.thread;

import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class DifferenceWaitNotifyTest {

	protected class Worker {

		private volatile boolean done = false;

		public Worker() {
		}

		public synchronized void put() throws InterruptedException {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("T[" + Thread.currentThread().getId() + "] put finished");
			done = true;
			notify();
		}

		public synchronized boolean take() throws InterruptedException {
			while (!done) {
				System.out.println("T[" + Thread.currentThread().getId() + "] take waiting");
				wait();
			}
			return done;
		}
	}

	@Test
	public void worker() {
		Worker worker = new Worker();
		Thread threadA = new Thread(() -> {
			String id = "A";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				worker.put();
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " finished");
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		Thread threadB = new Thread(() -> {
			String id = "B";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				boolean done = worker.take();
				System.out.println(
						"T[" + Thread.currentThread().getId() + "] worker: " + id + " finished, result: " + done);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		threadB.start();
		threadA.start();

		try {
			threadA.join();
			threadB.join();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}

	protected class InterruptWorker {

		private volatile boolean done = false;

		public InterruptWorker() {
		}

		public synchronized void put() throws InterruptedException {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("T[" + Thread.currentThread().getId() + "] put finished");
			done = true;
			// notify();
		}

		public synchronized boolean take() throws InterruptedException {
			while (!done) {
				System.out.println("T[" + Thread.currentThread().getId() + "] take waiting");
				wait();
			}
			return done;
		}
	}

	@Test
	public void interruptWorker() {
		InterruptWorker worker = new InterruptWorker();
		Thread threadA = new Thread(() -> {
			String id = "A";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				worker.put();
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " finished");
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		Thread threadB = new Thread(() -> {
			String id = "B";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				boolean done = worker.take();
				System.out.println(
						"T[" + Thread.currentThread().getId() + "] worker: " + id + " finished, result: " + done);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		threadB.start();
		threadA.start();

		try {
			threadA.join();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}

		long startTime = System.currentTimeMillis();
		while (true) {
			if (System.currentTimeMillis() - startTime > 1000) {
				threadB.interrupt();
				break;
			}
		}
	}

	protected class IllegalMonitorWorker {

		private volatile boolean done = false;
		private final Object lock = new Object();

		public IllegalMonitorWorker() {
		}

		public void put() throws InterruptedException {
			synchronized (lock) {
				TimeUnit.SECONDS.sleep(3);
				System.out.println("T[" + Thread.currentThread().getId() + "] put finished");
				done = true;
				notify();
			}
		}

		public boolean take() throws InterruptedException {
			synchronized (lock) {
				while (!done) {
					System.out.println("T[" + Thread.currentThread().getId() + "] take waiting");
					wait();
				}
			}
			return done;
		}
	}

	@Test
	public void illegalMonitorWorker() {
		IllegalMonitorWorker worker = new IllegalMonitorWorker();
		Thread threadA = new Thread(() -> {
			String id = "A";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				worker.put();
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " finished");
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		Thread threadB = new Thread(() -> {
			String id = "B";
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] worker: " + id + " ready");
				boolean done = worker.take();
				System.out.println(
						"T[" + Thread.currentThread().getId() + "] worker: " + id + " finished, result: " + done);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});

		threadB.start();
		threadA.start();

		try {
			threadB.join();
			threadA.join();
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}
}

心得分享

Difference Between wait and notify Methods in Java 操作這兩個方法 wait 和 notify 時,都有可能會拋出 IllegalMonitorStateException ,例如:當 1 個執行緒持有物件 A 的鎖時,並試圖調用物件 B 的 wait 或 notify 時, 就會發生此種情況, Difference Between notify and wait 提供這兩個方法的區別與使用。

發佈留言