为什么我的 java 同步方法会出现这个错误?

why is this mistake happening in my java synchronized method?

我是java大一新生,学习了java中线程与wait() notify()的通信,发生了一些蠢事:
事情是这样的:我想通过多线程不断地设置人类和获取人类,这意味着结果是(杰克男,玛丽女,杰克男,玛丽女......)
这是我的代码:

class Human {
  private String name;
  private String sex;
  private boolean b = false;

  public synchronized void set(String name, String sex) {
    if (b) {
        try {
            this.wait();
        } catch (InterruptedException e) {}
    } 
    this.name = name;
    this.sex = sex;
    b = true;
    this.notify();         
  }      
  public synchronized void get() {
    if (!b) {
        try {
            this.wait();
        } catch (InterruptedException e) {}
    }  
    System.out.println(name+"  "+sex);
    b = false;
    this.notify();        
  }
}
class SetHuman implements Runnable {
  private Human h;
  SetHuman(Human h) {
    this.h = h;
  }
  public void run() {     
    int x = 0;
    while(true) {           
        if (x==0) {
            h.set("Jack","male");
        }  else {
            h.set("Mary","female");
        }
        x = (x+1)%2;
    }
  }
}
class GetHuman implements Runnable {
  private Human h;
  GetHuman(Human h) {
    this.h = h;
  }
  public void run() {
    while (true) {
        h.get();
    }
  }
}

class HumanDemo  {
  public static void main(String[]args) {
    Human h = new Human();
    SetHuman sh = new SetHuman(h);
    GetHuman gh = new GetHuman(h);
    
    Thread t1 = new Thread(sh);
    Thread t2 = new Thread(gh);
    
    t1.start();
    t2.start();
  }
}

当我 运行 HumanDemo 时,它起作用了:result

然后,我在我的同步函数set()和get()中添加了一个else判断,这件事发生了:

public synchronized void set(String name, String sex) {
    if (b) {
        try {
            this.wait();
        } catch (InterruptedException e) {}
    } else {
        this.name = name;
        this.sex = sex;
        b = true;
        this.notify();
     }
}      
public synchronized void get() {
    if (!b) {
        try {
            this.wait();
        } catch (InterruptedException e) {}
    }  else {
        System.out.println(name+"  "+sex);
        b = false;
        this.notify();
    }
}

new result

这是为什么?有人请告诉我为什么吗?谢谢^-^!

在您的第一个工作示例中,您的 set/get() 方法是互斥的。 this.wait() 将强制他们等待对方才能完成工作。

在您的第二个示例中,您使用 else 打破了它,因为无法保证在释放锁后哪个线程将获得 'this' 上的锁。这样,任意数量的 'set()' 可能会迷失到等待状态,永远不会将它们的值设置为 'this.name' 和 'this.sex'.

示例(gt=获取线程 st=设置线程):

main-method 启动线程 st

st: h.set("Jack","male"); b 为假 -> this.name = "Jack"; b=真; (现在无法保证 st 是否会再次执行,或者 gt 是否已经创建并会通过同步的 get() 方法在 'this' 上获得锁定。这次让 st 获得锁定。)

?main-method 启动线程 gt? (可能会晚些)

st: h.set("Mary","female"); b 为真 -> this.wait(); (现在 st 正在等待有人释放 'this' 上的锁。由于在 else 语句中设置了 this.name 和 this.sex,因此它永远不会设置其当前值,并且此调用使用 "Marry" 和 "female" 会丢失。所以下一个 gt 将执行。)

?main-method 启动线程 gt? (可能已经发生)

gt: b 为真 -> System.out.println(姓名+" "+性别); b = false ...(在方法结束时,gt 将释放 'this' 上的锁,现在 st 将离开等待状态并尝试为此获取锁。gt 也在尝试获取 'this' 再次。再次不能保证哪个线程将获得锁并现在可以执行。)

长话短说:

由于 set 方法中的 else,您正在丢弃 'random' 数量的 set 调用(因此不太可能存在交替顺序)。如果 set 方法中没有 else,它应该可以工作。尽管这会浪费对 get 的方法调用,只是 运行 进入等待状态 return 而没有完成任何工作。

wait() 的调用必须始终处于 while 循环中,如 the documentation 中所述。你正在这样做,这很好。但是你弄错的部分是 while 循环需要 inside synchronized 块才能线程安全:

while (!condition) {
    // Wrong --- another thread might call notify or notifyAll
    // when the program is at this point, where this thread
    // will not detect it.
    synchronized (h) {
        h.wait();
    }
}

while 循环必须始终在同步块内以确保线程安全:

synchronized (h) {
    while (!condition) {
        h.wait();
    }
}

此外,中断是向您的线程发出的信号,表示有人希望它终止。不要忽视它。

到目前为止,最简单的方法是将整个循环放在 try/catch 中,这样 InterruptedException 将自动结束循环:

try {
    synchronized (h) {
        while (!condition) {
            h.wait();
        }
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

最后,永远不要写一个空的 catch 块。隐藏异常会使您的代码故障排除变得极其困难。