笔试题(1)--两个线程交叉打印递增数字,用synchronized和wait实现

朋友在最近的笔试中遇到一道笔,如下:

开启两个线程,一个线程打印1到100的奇数。如1,3,7…99. 另外一个线程打印1到100的偶数。如2,4,6…100.
1到100的数字最终打印出来格式是1,2,3,4,5…100. 要求用synchronized和wait实现.

Talk is cheap,show me the code:

public class WaitTest {
    static int[] share = new int[1];
    static int count = 0;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (share) {
                    while (count <= 100) {
                        try {
                            System.out.println("进入 thread 1 count=" + count);
                            //唤醒等待share资源的线程,把锁交给线程(该同步锁执行完毕自动释放锁或遇到wait方法自动释放锁)
                            share.notify();
                            System.out.println("thread 1 唤醒 share");
                            System.out.println("thread 1 print " + count++);
                            //释放CPU控制权,释放share的锁,本线程阻塞,等待被唤醒。
                            share.wait();
                            System.out.println("thread 1 wait share end count=" + count);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (share) {
                    while (count <= 100) {
                        try {
                            System.out.println("进入 thread 2 count=" + count);
                            share.notify();
                            System.out.println("thread 2 唤醒 share");
                            System.out.println("thread 2 print " + count++);
                            share.wait();
                            System.out.println("thread 2 wait share end count=" + count);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }
}
运行结果:
进入 thread 1 count=0
thread 1 唤醒 share
thread 1 print 0
进入 thread 2 count=1
thread 2 唤醒 share
thread 2 print 1
thread 1 wait share end count=2
进入 thread 1 count=2
thread 1 唤醒 share
thread 1 print 2
thread 2 wait share end count=3
进入 thread 2 count=3
thread 2 唤醒 share
……

thread 1和2 交替打印数字,完美!当然了,这代码是我用idea敲出来的,要真手写出还得完全弄懂wait和notify机制,下面就来完全搞懂它。

wait/notify/notifyAll

wait、notify 和 notifyAll属于Object类的自带方法,所以全部的类都有这三方法。
可以用 wait、notify 和 notifyAll 来实现线程间的通信,比如上述笔试题中打印的数字就是在两个线程的协同下完成的。wait方法相当于让当前线程交出此对象的锁,notify方法相当于唤醒一个正在等待对象锁的线程,notify方法则是唤醒在此对象锁上等待的所有线程。

理解以下几点:

  1. wait和notify/notifyAll方法必须在在同步代码块中使用,为什么?
    java设计者为了避免wait和notify之间产生竞态条件,所以强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。
    调用某个对象的wait()方法能让当前线程阻塞,前提是当前线程必须拥有此对象的锁,因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
  2. 来实现线程间的通信,为什么wait、notify 和 notifyAll这些方法不在Thread类里面?
    我想主要原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
  3. wait和notify方法的执行过程。
    其实,光看上述题目的代码和运行结果,对执行过程也能理解个八九不离十。
    share数组作为对象锁,thread 1首先进入synchronized的同步块中,thread 2阻塞在share的对象锁。thread 1进入while循环打印日志,执行share.notify方法,这里会唤醒等待share对象锁资源的线程,待thread 1执行完同步块的内容会自动释放锁资源接着打印日志,执行share.wait方法,thread 1交出当前的对象锁,阻塞在此,以至于后续的share end还未打印。
    thread 2终于拿到了share锁资源,开始执行while,打印日志,执行share.notify方法,同样的唤醒thread 1,但得走完才能移交锁,接着打印日志,执行share.wait,thread 2阻塞在此,移交锁资源给thread 1。 thread 1接到资源后,从原来阻塞地点又开始运行了,打印share end日志,进入下一次循环…… 如此往复。
    值得注意的是,调用notify或notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块执行完或者调用了wait方法才会让出锁资源。
  4. 有多个线程在wait时,notifyAll唤醒的是哪个线程?
    notify()和notifyAll()方法只是唤醒等待该对象的monitor(监视器)的线程,并不决定哪个线程能够获取到监视器。所以调完nofityAll()方法后,不确定哪个线程能获取到对象锁资源。
  5. 一定是在循环中调用 wait 和 notify,而不是在 if语句中?
    至于为什么,大家想想吧

你可能感兴趣的:(java,面试)