用object类的wait跟notify方法实现三个线程交替打印ABC

用object类的wait和notify方法实现三个线程交替打印ABC

1 JAVA doc对Object类的wait()和notify()有非常清晰明确的介绍

 

2 wait和notify在进行RPC时是非常有用的,进程A负责发请求的线程在发送了请求后,用wait挂住自身,进程B作为服务提供者处理完服务后,发应答给进程A,此时进程A的另外一个线程执行notify唤醒原来的线程,接收wait或notify消息的对象通常是一个放在缓存里面的request 

    -- 这个地方介绍的是如何进行同步调用

 

3  贴一个实现类,演练如何使用wait和notify编排线程的执行顺序,输出结果是:ABCABC....

  从测试的结果看应当是满足了需求,增加执行次数,增加需要执行的任务数,线程的执行顺序依然受控

/**
 * 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC
 * 
 */
public class ThreadPrintABCOneByOne {

	private interface BusinessHandler {
		void doBusiness();
	}

	public static class ScheduledTask implements Runnable {
		private BusinessHandler handler; // 需要反复执行的业务代码

		private Object lock = new Object();

		private int maxTimes;

		private ScheduledTask next;

		private Thread thread; // 负责执行handler的线程

		public Thread getThread() {
			return thread;
		}

		public void setThread(Thread thread) {
			this.thread = thread;
		}

		public void setNext(ScheduledTask next) {
			this.next = next;
		}

		public void setHandler(BusinessHandler handler) {
			this.handler = handler;
		}

		public void setMaxTimes(int maxTimes) {
			this.maxTimes = maxTimes;
		}

		private void suspend() {
			synchronized (lock) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		}

		private void resumeNextAndSuspendCurrent() {
			synchronized (lock) {
				next.resume(); // 恢复下一个任务执行,执行此方法时,当前线程持有lock对象的监视器,
								// 如果其它线程试图唤醒当前线程,它将等待直到lock.wait执行
				try {
					lock.wait();
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		}

		public void resume() {
			synchronized (lock) {
				lock.notify();
			}
		}

		public void run() {
			suspend(); // 先把线程挂住,等待其他线程激活

			int executedTimes = 0;
			while (executedTimes < maxTimes) {
				handler.doBusiness();
				executedTimes++;

				resumeNextAndSuspendCurrent();
			}
		}

		public void start() {
			thread.start();
		}
	}

	private static ScheduledTask create(BusinessHandler handler, ScheduledTask parent, int handlerExeTimes) {
		ScheduledTask task = new ScheduledTask();
		task.setHandler(handler);
		task.setMaxTimes(handlerExeTimes); 
		if (parent != null) {
			parent.setNext(task);
		}
		Thread thread = new Thread(task);
		task.setThread(thread);

		return task;
	}

	public static void main(String[] args) throws InterruptedException {
		final int handlerExeTimes = 10;

		BusinessHandler handlerA = new BusinessHandler() {
			public void doBusiness() {
				System.out.println("A");
			}
		};
		ScheduledTask taskA = create(handlerA, null, handlerExeTimes);

		BusinessHandler handlerB = new BusinessHandler() {
			public void doBusiness() {
				System.out.println("B");
			}
		};
		ScheduledTask taskB = create(handlerB, taskA, handlerExeTimes); // A -->B

		BusinessHandler handlerC = new BusinessHandler() {
			public void doBusiness() {
				System.out.println("C");
			}
		};
		ScheduledTask taskC = create(handlerC, taskB, handlerExeTimes); // B --> C

		taskC.setNext(taskA); // C-->A

		// 任务的执行顺序编排完毕,开始执行
		taskA.start();
		taskB.start();
		taskC.start();
		// 等待100ms,确保所有线程全部就绪
		Thread.sleep(100);
		// 调度第一个任务执行
		taskA.resume();

		Thread.sleep(Long.MAX_VALUE); // 挂住主线程
	}

}

 

4 上述代码使用wait和notify时,没有考虑虚假唤醒的可能,按照JDK里面的说法:

 

对于某一个参数的版本,实现中断和虚假唤醒是有可能的,并且此方法应始终在循环中使用:  

synchronized (obj) {

while (<condition does not hold>)

obj.wait(timeout, nanos);

... // Perform action appropriate to condition

 

     }

 

所以我们需要对代码进行修改,造一个循环出来,为了造这个循环,引入了suspend这个标记位,改造后的代码如下

public static class ScheduledTask implements Runnable {
		private volatile boolean suspended = false;

		private void suspend() {
			synchronized (lock) {
				suspend0();
			}
		}

		private void resumeNextAndSuspendCurrent() {
			synchronized (lock) {
				next.resume(); // 恢复下一个任务执行,执行此方法时,当前线程持有lock对象的监视器,
								// 如果其它线程试图唤醒当前线程,它将等待直到lock.wait执行
				suspend0();
			}
		}

		private void suspend0() {
			suspended = true; // 设置挂起标记  
			while (suspended) { // 只要挂起标记没有被清除,一直挂住当前线程  
				try {
					lock.wait();
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		}

		public void resume() {
			synchronized (lock) {
				suspended = false; // 清除挂起标记  
				lock.notify();
			}
		}

	  ...
	} 

 

请注意suspended 应当被volatile所修饰,重新测试一下,还是输出ABCABC... 功能没变,引入的标记位没有引发bug,状态维护没有缺失   -- 引入标记位是一件需要慎重考虑的事

 

扯的远一点,关于虚假唤醒的处理,可以参照JDK里面的PriorityQueue和其子类,可以看到更多的同步方式