【问题标题】:why is this mistake happening in my java synchronized method?为什么在我的 java 同步方法中会发生这个错误?
【发布时间】:2017-11-27 14:28:31
【问题描述】:

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

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

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

【问题讨论】:

  • 您能在不使用图像的情况下解释问题吗?这个不清楚
  • 哦,对不起,我的意思是我需要将这两个人类打印一个,(杰克男,玛丽女,杰克男,玛丽女......)第一个工作,但是第二个,我加了else判断后,synchronized方法出了问题,就变成不同步了。

标签: java multithreading synchronized


【解决方案1】:

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

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

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

main-method 启动线程 st

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

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

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

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

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

长话短说:

由于 set 方法中的 else ,您正在丢弃“随机”数量的 set 调用(因此不太可能出现交替顺序)。如果没有 set 方法中的 else 它应该可以工作。虽然这会浪费对 get 的方法调用,而这些调用只是进入等待状态而没有完成任何工作就返回。

【讨论】:

    【解决方案2】:

    wait() 的调用必须始终处于while 循环中,如the documentation 中所述。你正在这样做,这很好。但是您弄错的部分是 while 循环需要 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 块。隐藏异常会使您的代码故障排除变得非常困难。

    【讨论】:

      猜你喜欢
      • 2015-03-19
      • 1970-01-01
      • 2015-10-05
      • 1970-01-01
      • 2014-06-19
      • 2013-06-06
      • 2014-07-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多