用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和其子类,可以看到更多的同步方式