最近看帖子,发现一道面试题:
启动两个线程, 一个输出 1,3,5,7…99, 另一个输出 2,4,6,8…100 最后 STDOUT 中按序输出 1,2,3,4,5…100
题目要求用 Java 的 wait + notify 机制来实现,重点考察对于多线程可见性的理解。
wait 和 notify 均为 Object 的方法:
从以上的定义中,我们可以了解到以下事实:
wait()
和 notify()
来实现不同的线程间的可见。 在使用 wait 和 notify 之前,我们需要先了解对象的控制权(monitor)。在 Java 中任何一个时刻,对象的控制权只能被一个线程拥有。如何理解控制权呢?请先看下面的简单代码:
public class ThreadTest { public static void main(String[] args) { Object object = new Object(); new Thread(new Runnable() { @Override public void run() { try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
直接执行,我们将会得到以下异常:
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.xiangyu.demo.ThreadTest$1.run(ThreadTest.java:10) at java.lang.Thread.run(Thread.java:748)
出错的代码在: object.wait();
。这里我们需要了解以下事实:
在上面的示例代码中,我们 new 了一个 Thread,但是对象 object 的控制权仍在主线程里。所以会报 java.lang.IllegalMonitorStateException 。
我们可以通过同步锁来获得对象控制权,例如:synchronized 代码块。对以上的示例代码做改造:
public class ThreadTest { public static void main(String[] args) { Object object = new Object(); new Thread(new Runnable() { @Override public void run() { synchronized (object){ // 修改处 try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
再次执行,代码不再报错。
了解了对象控制权之后,我们就可以正常地使用 notify 和 wait 了,下面给出我的解题方法,供参考。
public class ThreadTest { private final Object flag = new Object(); public static void main(String[] args) { ThreadTest threadTest = new ThreadTest(); ThreadA threadA = threadTest.new ThreadA(); threadA.start(); ThreadB threadB = threadTest.new ThreadB(); threadB.start(); } class ThreadA extends Thread { @Override public void run() { synchronized (flag) { for (int i = 0; i <= 100; i += 2) { flag.notify(); System.out.println(i); try { flag.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class ThreadB extends Thread { @Override public void run() { synchronized (flag) { for (int i = 1; i < 100; i += 2) { flag.notify(); System.out.println(i); try { flag.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }