java交替打印奇偶数问题,会出现2个线程都wait阻塞了

tengqingya
  • 417

代码如下:

import static java.lang.System.out;

/**
 * 交替打印奇偶数
 *
 * @author ***
 * @create 2018-11-13 19:48
 */
public class Test0001 {
    public static Object OBJ = new Object();
    public static int i;

    public static class MyThread1 extends Thread {
        @Override
        public void run() {
            while( true ) {
                synchronized( OBJ ) {
                    out.println(Thread.currentThread().getName() + "  enter----  " + i);
                    if( i % 2 == 0 ) {
                        out.println(Thread.currentThread().getName() + "    " + i++);
                        try {
                            out.println(Thread.currentThread().getName() + "    wait");
                            OBJ.wait();
                            out.println(Thread.currentThread().getName() + "    wait over");
                        } catch( InterruptedException e ) {
                            e.printStackTrace();
                        }
                    } else {
                        out.println(Thread.currentThread().getName() + "    notify");
                        OBJ.notify();
                    }
                    out.println(Thread.currentThread().getName() + "leave---      " + i);
                }
            }
        }
    }

    public static class MyThread2 extends Thread {
        @Override
        public void run() {

            while( true ) {
                synchronized( OBJ ) {
                    out.println(Thread.currentThread().getName() + "  enter----  " + i);
                    if( i % 2 == 1 ) {
                        out.println(Thread.currentThread().getName() + "    " + i++);
                        out.println(Thread.currentThread().getName() + "    notify");
                        OBJ.notify();
                    } else {
                        try {
                            out.println(Thread.currentThread().getName() + "    wait");
                            OBJ.wait();
                            out.println(Thread.currentThread().getName() + "    wait over");
                        } catch( InterruptedException e ) {
                            e.printStackTrace();
                        }
                    }
                    out.println(Thread.currentThread().getName() + "leave---      " + i);
                }
            }
        }
    }

    public static void main( String[] args ) {
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        t1.start();

        try {
            Thread.sleep(111);
        } catch( InterruptedException e ) {
            e.printStackTrace();
        }
        t2.start();
    }
}

执行结果如下:

Thread-0  enter----  0
Thread-0    0
Thread-0    wait
Thread-1  enter----  1
Thread-1    1
Thread-1    notify
Thread-1leave---      2    //语句1
Thread-1  enter----  2     //语句2
Thread-1    wait
Thread-0    wait over
Thread-0leave---      2
Thread-0  enter----  2
Thread-0    2
Thread-0    wait

然后程序就卡住了····
好奇的是,为什么 Thread-1 leave(语句1) 之后 Thread-1 又立马 enter(语句2) 了,按道理 Thread-1 leave之后不是应该 Thread-0 enter吗。。。求赐教!!

回复
阅读 1.7k
3 个回答

我来回答一波。

你可以先将 Thread2 的代码做以下两种修改,一种是:

    public static class MyThread2 extends Thread {
        @Override
        public void run() {

            while( true ) {
                synchronized( OBJ ) {
                    out.println(Thread.currentThread().getName() + "  enter----  " + i);
                    if( i % 2 == 1 ) {
                        out.println(Thread.currentThread().getName() + "    " + i++);
                        out.println(Thread.currentThread().getName() + "    notify");
                        OBJ.notify();
                    } else {
                        try {
                            out.println(Thread.currentThread().getName() + "    wait");
                            OBJ.wait();
                            out.println(Thread.currentThread().getName() + "    wait over");
                        } catch( InterruptedException e ) {
                            e.printStackTrace();
                        }
                    }
                    out.println(Thread.currentThread().getName() + "leave---      " + i);
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

另外一种是:

    public static class MyThread2 extends Thread {
        @Override
        public void run() {

            while( true ) {
                synchronized( OBJ ) {
                    out.println(Thread.currentThread().getName() + "  enter----  " + i);
                    if( i % 2 == 1 ) {
                        out.println(Thread.currentThread().getName() + "    " + i++);
                        out.println(Thread.currentThread().getName() + "    notify");
                        OBJ.notify();
                    } else {
                        try {
                            out.println(Thread.currentThread().getName() + "    wait");
                            OBJ.wait();
                            out.println(Thread.currentThread().getName() + "    wait over");
                        } catch( InterruptedException e ) {
                            e.printStackTrace();
                        }
                    }
                    out.println(Thread.currentThread().getName() + "leave---      " + i);
                }
               Thread.yield();
            }
        }
    }

这两种改法你分别运行一下,会发现第一种改法运行以后能得到你想要的结果。
而第二种改法,答案是不一定,并且最终极大可能还是会把两个线程堵死。

你的代码的运行结果跟你的预期不符,主要你忽视了两点:

  • CPU时间片
  • wait被notify后,不会立即获得锁。

Java 的对象锁的实现采用的是Monitor机制,具体机制如下,你也可以参考我写过的一篇文章

图片描述

当一个线程需要获取 Object 的锁时,会被放入 EntrySet 中进行等待,如果该线程获取到了锁,成为当前锁的 owner。如果根据程序逻辑,一个已经获得了锁的线程缺少某些外部条件,而无法继续进行下去(例如生产者发现队列已满或者消费者发现队列为空),那么该线程可以通过调用 wait 方法将锁释放,进入 wait set 中阻塞进行等待,其它线程在这个时候有机会获得锁,去干其它的事情,从而使得之前不成立的外部条件成立,这样先前被阻塞的线程就可以重新进入 EntrySet 去竞争锁。

你的Thread2调完notify后,Thread1等到Thread2释放锁以后,它还得和 Thread2 站在同一条起跑线上去竞争锁,所以没有绝对地说Thread2释放锁以后,Thread1一定就轮到它获得锁。

你的代码每次都运行相同的结果,原因主要在于 Thread2 会比 Thread1 更容易重新获得锁,因为Thread2的CPU时间片极大可能还没用完。

我们上面的修改,第一种修改是让 Thread2 sleep 一秒,这绝对能让Thread1 有充足的时间获取锁,所以运行结果会如你所预期。
第二种修改是让 Thread2 让出 CPU 时间片,但是同样,CPU 在重新选择线程运行的时候,至少会有一半的机会还是会选中 Thread2。

以上。希望能帮到你。

没看具体逻辑,大概你查这2个问题:
第一:逻辑判断不要用if,用while,具体原因自己去查
第二:保证每个出口都要设置notifyAll并且建议不要用notify,保持公平策略的话,考虑使用显式锁

你知道吗?

宣传栏