【问题标题】:synchronized, wait/notifyAll has to be on the same object, but why?同步,wait/notifyAll 必须在同一个对象上,但是为什么呢?
【发布时间】:2019-08-21 02:36:00
【问题描述】:

当我尝试使用wait()synchronized 进行简单演示时,我突然想到了一件有趣的事情,以下演示给了我意外的输出。

public class WaitZero {
    private static AtomicInteger num = new AtomicInteger(0);
    private static boolean consumed = false;

    public static void main(String... args) throws Exception {
        ThreadPoolExecutor threadPoolExecutor = getMyCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(WaitZero::send);
            threadPoolExecutor.submit(WaitZero::receive);
        }
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static synchronized void send() {
        try {
            while (!isConsumed()) {
                num.wait();
            }
        } catch (InterruptedException ignored) {
            ignored.printStackTrace();
        }
        num.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + " number updated: " + num);
        setConsumed(false);
        num.notifyAll();
    }

    private static synchronized void receive() {
        try {
            while (isConsumed()) {
                num.wait();
            }
        } catch (InterruptedException ignored) {
            ignored.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " number received: " + num);
        setConsumed(true);
        num.notifyAll(); // ToDo: when to use notify?
        // ToDo: what is monitor?
    }

    private static boolean isConsumed() {
        return consumed;
    }

    private static void setConsumed(boolean consumed) {
        WaitZero.consumed = consumed;
    }
}

它的输出不稳定,但典型的一种可以

shared-pool-0 number received: 0
shared-pool-1 number updated: 1
shared-pool-0 number received: 1
shared-pool-1 number updated: 2
shared-pool-1 number received: 2
shared-pool-2 number updated: 3

而我所期待的是

shared-pool-1 number received: 0
shared-pool-0 number updated: 1
shared-pool-3 number received: 1
shared-pool-2 number updated: 2
shared-pool-1 number received: 2
shared-pool-0 number updated: 3
shared-pool-2 number received: 3
shared-pool-3 number updated: 4
shared-pool-5 number received: 4
shared-pool-4 number updated: 5

当我在wait()/notifyAll() 上使用WaitZero.class 而不是num 时检索到正确的结果。

我已经阅读过,似乎总是必须在同一个对象上使用它们中的三个以确保正确性。

我的猜测:如果不是所有这些都在同一个对象上,notifyAll() 和同步锁之间存在特殊情况。但它是什么?

任何帮助将不胜感激;)

【问题讨论】:

  • 你的方法是同步的,因为它们是静态的,这意味着它使用方法的封闭类的监视器。这就是当您在WaitZero.class 上调用wait/notify/notifyAll 方法时它起作用的原因,因为您在同一个Class 对象上进行同步。它不适用于num,因为您从不同步它——也不应该因为它是一个非最终的、积极变化的参考。
  • wait 和 notifyAll 都要求线程持有锁监视器。他们从不这样做,因此总是抛出 IllegalMonitorStateException。并且您通过使用 num++ 继续为 num 分配一个新值,因此您甚至不会在同一个对象上调用 wait 和 notifyAll 。 1.不要使用像0这样的共享值作为锁。使用您自己创建的私有最终锁定对象。 2.总是同步、等待和通知锁。 3. 首先避免这些低级、容易出错的同步原语。使用来自 java.util.concurrent 的更高级别的抽象。
  • @Hearen Integer 是不可变的,因此当您执行 num++ 时,您正在创建一个具有递增值的新 Integer 实例。所以对象发生了变化。
  • 没有。 num++ 等价于 num = Integer.valueOf(num.intValue() + 1)。它将另一个对象分配给num 变量。 0 是一个共享的缓存整数值。因此任何其他线程也可能在其上同步,从而导致干扰、死锁等。
  • 同样,wait 和 notifyAll 都要求线程持有锁监视器。他们从不这样做,因此总是抛出 IllegalMonitorStateException。

标签: java java-8 wait synchronized


【解决方案1】:

在@JB Nizet、@Amardeep Bhowmick 等人提出了很多天真的问题和大力帮助之后,我从How to work with wait(), notify() and notifyAll() in Java? 中找到了一句精辟的句子,准确地解释了原因。

wait() 方法被设计/用于放弃锁(因为某些条件不满足)让其他线程工作/合作;典型的用例是发送者/接收者或生产者/消费者。

wait()

它告诉调用线程放弃并进入睡眠状态,直到其他线程进入同一个监视器并调用notify()...@987654328 @ 方法实际上与同步锁紧密集成,使用了同步机制无法直接提供的功能。

synchronized(lockObject) {
    while( ! condition ) {
        lockObject.wait();
    }
    //take the action here;
}

在这种情况下,问题可以简单地修复如下,或者只使用WaitZero.class 代替wait/notifyAll

public class WaitZero {
    private static AtomicInteger num = new AtomicInteger(0);
    private static boolean consumed = false;

    public static void main(String... args) throws Exception {
        ThreadPoolExecutor threadPoolExecutor = getMyCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(WaitZero::send);
            threadPoolExecutor.submit(WaitZero::receive);
        }
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static void send() {
        synchronized (num) {
            try {
                while (!isConsumed()) {
                    num.wait();
                }
            } catch (InterruptedException ignored) {
                ignored.printStackTrace();
            }
            num.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + " number updated: " + num);
            setConsumed(false);
            num.notifyAll();
        }
    }

    private static void receive() {
        synchronized (num) {
            try {
                while (isConsumed()) {
                    num.wait();
                }
            } catch (InterruptedException ignored) {
                ignored.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " number received: " + num);
            setConsumed(true);
            num.notifyAll(); // ToDo: when to use notify?
            // ToDo: what is monitor?
        }
    }

    private static boolean isConsumed() {
        return consumed;
    }

    private static void setConsumed(boolean consumed) {
        WaitZero.consumed = consumed;
    }
}

【讨论】:

  • 当所有对变量的访问都是synchronized 时,使用AtomicInteger 是没有意义的。除此之外,你发现的只是original documentation of wait中已经写的内容
  • @Holger,早上好,很抱歉这么晚才回复。我使用AtomicInteger 确实是不必要的,也许我可以只使用Long 来避免IntegerCache 参考更改问题。是的,我发现的所有内容都直接显示在文档中,但当时我读了很多次,仍然无法理解;这就是为什么我一直在努力。就像常说的那样,除非犯错,否则您永远不会理解它。事实上,很多问题都可以追溯到官方文档或教程,但只要它让我感到困惑,它就可以让其他人感到困惑。祝你有美好的一天;)
  • 您可以使用普通的int 并使用不同的对象进行同步。 IE。在您的原始代码中,您有声明为static synchronized 的方法,它使用WaitZero.class 进行隐式锁定,可与WaitZero.class.wait()WaitZero.class.notify() 结合使用。或者在int 字段之外创建一个类似private static final Object LOCK = new Object(); 的字段并使用synchronized(LOCK) { … }。你是对的,阅读规范并不总是有帮助,但值得将它作为一个起点,以后再阅读它以加深理解。
  • 您可能还阅读了thisthat 的答案,这些是为Lock 实现编写的,但逻辑也适用于synchronized
  • @Holger 感谢您提供更具可读性的答案。非常周到和乐于助人,非常感谢!我会调查他们并更多地掌握这个话题。谢谢你,霍尔格
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多