为什么两个线程交替输出中添加一个无效的判定条件会使得交替输出终止?

kathy_koo 2020-08-09 10:24:20

package com.test.notify;

public class WaitNotifyTest {
static Object lock = 1;
public static void main(String[] args) throws InterruptedException{
new PingPong("t1", 0).start();
new PingPong("t2", 1).start();
}
}

class PingPong extends Thread{
int i;
String name;
PingPong(String name, int i){
this.name = name;
this.i = i;
}
@Override
public void run(){
for (int i = this.i; i<20; i+=2){
try {
synchronized (WaitNotifyTest.class){
System.out.println(this.name + ": notify");
WaitNotifyTest.class.notify();
System.out.println(this.name + ": " + i);
//while (i!=20){//移除注释运行会出错
System.out.println(this.name + ": wait");
WaitNotifyTest.class.wait();
//}
}
}
catch (InterruptedException e){
System.err.println(this.name + ": " + e.getMessage());
}
}
}
}


这段代码 运行输出

/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/java -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/tools.jar:/Users/gucci/Documents/java_course/Homework2/target/classes:/Users/gucci/.m2/repository/org/apache/poi/poi/3.14/poi-3.14.jar:/Users/gucci/.m2/repository/commons-codec/commons-codec/1.10/commons-codec-1.10.jar:/Users/gucci/.m2/repository/org/apache/pdfbox/pdfbox/2.0.13/pdfbox-2.0.13.jar:/Users/gucci/.m2/repository/org/apache/pdfbox/fontbox/2.0.13/fontbox-2.0.13.jar:/Users/gucci/.m2/repository/commons-logging/commons-logging/1.2/commons-logging-1.2.jar com.test.notify.WaitNotifyTest
t1: notify
t1: 0
t1: wait
t2: notify
t2: 1
t2: wait
t1: notify
t1: 2
t1: wait
t2: notify
t2: 3
t2: wait
t1: notify
t1: 4
t1: wait
t2: notify
t2: 5
t2: wait
t1: notify
t1: 6
t1: wait
t2: notify
t2: 7
t2: wait
t1: notify
t1: 8
t1: wait
t2: notify
t2: 9
t2: wait
t1: notify
t1: 10
t1: wait
t2: notify
t2: 11
t2: wait
t1: notify
t1: 12
t1: wait
t2: notify
t2: 13
t2: wait
t1: notify
t1: 14
t1: wait
t2: notify
t2: 15
t2: wait
t1: notify
t1: 16
t1: wait
t2: notify
t2: 17
t2: wait
t1: notify
t1: 18
t1: wait
t2: notify
t2: 19
t2: wait


显然两个线程在交替输出符合我的预期

但当我把while(i!= 20) 这个永远为true的条件解注释后,运行结果如下


t1: notify
t1: 0
t1: wait
t2: notify
t2: 1
t2: wait
t1: wait


我理解不了这个情况 希望有人能解释一下
...全文
2002 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
加上条件有问题吧,你注释错了吧?
大隐藏于寺 2020-08-10
  • 打赏
  • 举报
回复
t1: notify
t1: 0
t1: wait
t2: notify
t2: 1
t2: wait
t1: wait
根据上面的输出来分析,首先是t1执行第一次,获取到锁,唤醒等待锁的线程t2,打印t1: notify,t1: 0,紧接着进入了while循环的判断,此时i不等于20,执行while循环方法体,打印t1: wait,释放锁等待唤醒,注意此时没有跳出while循环,只是t1线程进入等待锁的状态,下次获取到锁后继续执行while循环. t2获取到锁,唤醒等待锁的t1,打印t2: notify,t2: 1,同样的进入了while循环判断,此时i为1不等于20,执行while循环方法体,打印t2: wait,释放锁等待唤醒,下次获取到锁后继续执行while循环. t1获取到锁,继续执行while循环,此时i还是0,不等于20,打印t1: wait,释放锁等待唤醒,注意此时两个线程都进入了等待唤醒的状态,如下图 估计你的盲点是执行完wait()方法后线程再次获取到锁时,应该如何执行.执行wait()方法时会保存当前上下文,也就是当前时间点 CPU 寄存器和程序计数器的内容,下次获取到锁后,会从保存的上下文加载当时的状态,继续执行wait()方法后续的代码,这其实就是上下文切换.为了验证这个问题,可以在while循环的方法体中在wait()方法后加一句System.out.println("这是恢复后执行的代码"+this.name);整体来看就是
package com.test.notify;

public class WaitNotifyTest {
    static Object lock = 1;
    public static void main(String[] args) throws InterruptedException{
        new PingPong("t1", 0).start();
        new PingPong("t2", 1).start();
    }
}

class PingPong extends Thread{
    int i;
    String name;
    PingPong(String name, int i){
        this.name = name;
        this.i = i;
    }
    @Override
    public void run(){
        for (int i = this.i; i<20; i+=2){
            try {
                synchronized (WaitNotifyTest.class){
                    System.out.println(this.name + ": notify");
                    WaitNotifyTest.class.notify();
                    System.out.println(this.name + ": " + i);
                    while (i!=20){
                        System.out.println(this.name + ": wait");
                        WaitNotifyTest.class.wait();
                        //这是新添加的代码
                        System.out.println("这是恢复后执行的代码"+this.name);
                   }
                }
            }
            catch (InterruptedException e){
                System.err.println(this.name + ": " + e.getMessage());
            }
        }
    }
}
控制台输出结果:

t1: notify
t1: 0
t1: wait
t2: notify
t2: 1
t2: wait
这是恢复后执行的代码t1
t1: wait

从打印结果来看"这是恢复后执行的代码t1"比后一次"t1: wait"先打印,也就证明了wait()后的线程再次获取到锁时,继续执行的是wait()方法后的代码.
kathy_koo 2020-08-10
  • 打赏
  • 举报
回复
关键是不会跳出while循环,我傻了,我的错,谢谢指导
xuduo520 2020-08-10
  • 打赏
  • 举报
回复
线程1进入循环后进入睡眠状态,线程2唤醒了线程1之后,线程2进入循环进入睡眠状态,但是这个时候线程1还是处于循环中,所以线程1继续执行循环体代码,又进入了睡眠状态.至此,两个线程全部进入无限等待状态了.解决办法,在循环体增加i++,并在睡眠之前执行一次唤醒操作
dkwuxiang 2020-08-10
  • 打赏
  • 举报
回复
  while (i!=20){//移除注释运行会出错
                        System.out.println(this.name + ": wait");
                        WaitNotifyTest.class.wait();
                    }
循环代码存在时, t1 执行等待后,释放 对象锁,t2 执行,t2 notify 唤醒t1,t2 执行 wait后 释放对象锁,t1 被唤醒继续执行,又进入 while循环,执行wait, 现在 t1,t2 全部处于等待,死锁了;
qq_39936465 2020-08-10
  • 打赏
  • 举报
回复
引用 楼主 kathy_koo 的回复:
[code=java] 我理解不了这个情况 希望有人能解释一下
要记住1个wait必定对应一个notify不然会死锁,你1个notify对应循环里的20个wait,肯定死锁。

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧