【问题标题】:java wait and notifyjava等待并通知
【发布时间】:2016-04-12 07:35:53
【问题描述】:

我正在使用一个整数变量并与两个线程共享。一个线程应该打印偶数,一个线程应该顺序打印奇数。 但是 notify() 会抛出 IllegalMonitorStateException。

package mywaitnotifytest;
public class App {

    public static void main(String[] args) {
        Integer i=0;
        Even even = new Even(i);
        even.setName("EvenThread");
        Odd odd = new Odd(i);
        odd.setName("OddThread");
        even.start();
        odd.start();
    }
}

class Even extends Thread{

    Integer var;

    Even(Integer var){
        this.var=var;
    }

    @Override
    public void run() {
        while(true){
            synchronized (var) {
                if(var%2==0){
                    try {
                        var.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                var++;
                System.out.println(Thread.currentThread().getName()+"  "+var);
                var.notify();
            }
        }

    }
}

class Odd extends Thread{

    Integer var;

    Odd(Integer var){
        this.var=var;
    }

    @Override
    public void run() {
        while(true){
            synchronized (var) {
                if(var%2!=0){
                    try {
                        var.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                var++;
                System.out.println(Thread.currentThread().getName()+"  "+var);
                var.notify();
            }
        }
    }
}

输出是:

奇线程 1

Exception in thread "OddThread" java.lang.IllegalMonitorStateException

at java.lang.Object.notify(Native Method)

at mywaitnotifytest.Odd.run(App.java:67)

【问题讨论】:

  • 这个问题似乎有所不同,因为 OP 在notify 上遇到异常,而不是wait。加上异常的原因是完全不同的,与非synchronized代码无关
  • 您没有在锁定的同一对象上调用notify()。简而言之,不要锁定可变字段。当你改变它时,你改变它。也不要锁定池对象,因为 Integer 是这样,这会产生令人困惑的后果。
  • 我只从同步块调用等待和通知
  • @ortis 当 OP 使用 synchornized 时,他/她没有在他们拥有 synchronized 的对象上调用通知。
  • @NareshMuthyala 使用synchronized 意味着您必须在同一个对象上调用notify/wait。它不会授予您锁定任何对象的权限。

标签: java multithreading wait notify


【解决方案1】:

我认为这与通常的答案有很大不同,可以给出另一个答案。

在这种情况下,您使用的是synchronized。当您应用锁时,它是在对象而不是引用上。


synchronized (var) {

这会锁定对象var 引用,而不是锁定var 作为字段。


var++;

这将替换 var 指向的对象。是一样的

var = Integer.valueOf(var.intValue() + 1);

注意:Integer 实际上所有原始包装器都是不可变的。当您对它们执行任何操作时,您实际上是在拆箱,使用原始值进行计算并重新装箱对象。如果它被池化,则可以取回相同的对象。例如

Integer i = 10;
i += 0; // gives back the same object.

但是,如果对象没有被池化

Double d = 10;
d += 0; // creates a new object.

var.notify();

尝试在新对象上调用notify,而不是被锁定的对象。


您不应该尝试锁定您更改的字段。它不会做它看起来做的事情。您也不应该锁定池对象。在这种情况下,您可以让另一个线程使用相同的Integer 用于不相关的目的,而notify() 将唤醒一个不相关的线程。

要正确使用等待/通知,您应该

  • notify()notifyAll() 在另一个共享字段中的状态更改之后。
  • 您应该对wait() 使用while 循环来检查状态变化。

如果你不这样做

  • 如果没有其他线程等待,通知可能会丢失。
  • wait 可能会虚假唤醒,即使没有调用 notify。

对于上述要求,代码中建议的编辑是什么?如何为多个线程共享同一个对象?

public class PingPong implements Runnable {    
    static class Shared { int num; }

    private final Shared var;
    private final int bit;

    public static void main(String[] args) {
        Shared var = new Shared();
        new Thread(new PingPong(var, 0), "EvenThread").start();
        new Thread(new PingPong(var, 1), "OddThread").start();
    }

    PingPong(Shared var, int bit) {
        this.var = var;
        this.bit = bit;
    }

    @Override
    public void run() {
        try {
            String name = Thread.currentThread().getName();
            while (true) {
                synchronized (var) {
                    while (var.num % 2 == bit)
                        var.wait();

                    var.num++;
                    System.out.println(name + "  " + var.num);
                    var.notify();
                }
            }
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
    }
}

【讨论】:

  • 这就是为什么在处理synchronizedwaitnotify 时应始终使用final 的原因
  • @ortis 在可能的情况下,使用序列化对象并不总是可以使用final,但该字段应该是有效的最终字段。 +1
  • 赞成这个答案。解释应该如何使用等待(稍后(conditionNotMet){waitOnTheLockObject;})。这个答案应该被接受。 @PeterLawrey,除此之外,如果您编辑并明确提及原语的所有包装类都是不可变的,那会很好。它将解释为什么 var++ on Integer 会(显式地)创建一个新的 Integer 对象。
  • @Amudhan 添加了更多解释。
【解决方案2】:

我没有使用整数包装类,而是创建了自己的类,现在它可以正常工作了。

package mywaitnotifytest;

public class App {
    public static void main(String[] args) {
        MyInt i = new MyInt(0);
        Even even = new Even(i);
        even.setName("EvenThread");
        Odd odd = new Odd(i);
        odd.setName("OddThread");
        even.start();
        odd.start();
    }
}

class Even extends Thread {

    MyInt var;

    Even(MyInt var) {
        this.var = var;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            synchronized (var) {
                if (var.i % 2 == 0) {
                    try {
                        var.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                var.i++;
                System.out.println(Thread.currentThread().getName() + "  " + var.i);
                var.notify();
            }
        }

    }

}

class Odd extends Thread {
    MyInt var;

    Odd(MyInt var) {
        this.var = var;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            synchronized (var) {
                if (var.i % 2 != 0) {
                    try {
                        var.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                var.i++;
                System.out.println(Thread.currentThread().getName() + "   " + var.i);
                var.notify();

            }
        }

    }
}

class MyInt {
    int i = 0;

    public MyInt(int i) {
        super();
        this.i = i;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return "" + i;
    }

}

【讨论】:

  • 发布解决方法而不解释原始问题的原因对读者来说是没有用的。请接受以上答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-30
  • 2013-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多