当前位置: 首页 > 知识库问答 >
问题:

Java:两个等待一个阻塞的线程,notify()导致活锁,notifyAll()没有,为什么?

费承载
2023-03-14

我试图使用Java同步“原语”(synchronized,wait(),notify())实现类似于Java的有界BlockingQueue接口的东西,这时我偶然发现了一些我不理解的行为。

我创建了一个能够存储 1 个元素的队列,创建两个等待从队列中获取值的线程,启动它们,然后尝试将两个值放入主线程同步块中的队列中。大多数情况下它可以工作,但有时等待值的两个线程开始似乎相互唤醒并且不让主线程进入同步块。

这是我的(简化)代码:

import java.util.LinkedList;
import java.util.Queue;

public class LivelockDemo {
    private static final int MANY_RUNS = 10000;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < MANY_RUNS; i++) { // to increase the probability
            final MyBoundedBlockingQueue ctr = new MyBoundedBlockingQueue(1);

            Thread t1 = createObserver(ctr, i + ":1");
            Thread t2 = createObserver(ctr, i + ":2");

            t1.start();
            t2.start();

            System.out.println(i + ":0 ready to enter synchronized block");
            synchronized (ctr) {
                System.out.println(i + ":0 entered synchronized block");
                ctr.addWhenHasSpace("hello");
                ctr.addWhenHasSpace("world");
            }

            t1.join();
            t2.join();

            System.out.println();
        }
    }

    public static class MyBoundedBlockingQueue {
        private Queue<Object> lst = new LinkedList<Object>();;

        private int limit;

        private MyBoundedBlockingQueue(int limit) {
            this.limit = limit;
        }

        public synchronized void addWhenHasSpace(Object obj) throws InterruptedException {
            boolean printed = false;
            while (lst.size() >= limit) {
                printed = __heartbeat(':', printed);
                notify();
                wait();
            }
            lst.offer(obj);
            notify();
        }

        // waits until something has been set and then returns it
        public synchronized Object getWhenNotEmpty() throws InterruptedException {
            boolean printed = false;
            while (lst.isEmpty()) {
                printed = __heartbeat('.', printed); // show progress
                notify();
                wait();
            }
            Object result = lst.poll();
            notify();
            return result;
        }

        // just to show progress of waiting threads in a reasonable manner
        private static boolean __heartbeat(char c, boolean printed) {
            long now = System.currentTimeMillis();
            if (now % 1000 == 0) {
                System.out.print(c);
                printed = true;
            } else if (printed) {
                System.out.println();
                printed = false;
            }
            return printed;
        }
    }

    private static Thread createObserver(final MyBoundedBlockingQueue ctr,
            final String name) {
        return new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(name + ": saw " + ctr.getWhenNotEmpty());
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
            }
        }, name);
    }
}

以下是我在它“阻塞”时看到的情况:

(skipped a lot)

85:0 ready to enter synchronized block
85:0 entered synchronized block
85:2: saw hello
85:1: saw world

86:0 ready to enter synchronized block
86:0 entered synchronized block
86:2: saw hello
86:1: saw world

87:0 ready to enter synchronized block
............................................

..........................................................................

..................................................................................
(goes "forever")

但是,如果我将addWhenHasSpace和getWhenNot空方法的同时(…)循环中的通知()调用更改为通知所有(),它“总是”通过。

我的问题是:为什么在这种情况下,notify()和notifyAll()方法的行为会有所不同,以及为什么notify的行为是这样的?

在这种情况下(两个线程在等待,一个线程被阻塞),我希望这两种方法表现相同,因为:

  1. 在我看来,在这种情况下,通知()只会唤醒另一个线程,与通知()相同;
  2. 唤醒线程的方法的选择似乎会影响被唤醒的线程(我猜是RUNNABLE)和主线程(已被BLOCKED)后来争夺锁的方式——这不是我对javadoc以及在互联网上搜索该主题的期望。

或者我完全做错了什么?

共有2个答案

苏昂雄
2023-03-14

似乎有某种公平/驳船正在使用内在锁定 - 可能是由于一些优化。我猜,本机代码会检查当前线程是否已通知它即将等待的监视器并允许它获胜。

用< code>ReentrantLock替换< code>synchronized,它应该如您所期望的那样工作。这里的不同之处在于< code>ReentrantLock如何处理它已经通知的锁的等待者。

更新:

有趣的发现。您看到的是main线程进入之间的竞赛

        synchronized (ctr) {
            System.out.println(i + ":0 entered synchronized block");
            ctr.addWhenHasSpace("hello");
            ctr.addWhenHasSpace("world");
        }

而其他两个线程进入各自的同步区域。如果主线程在两个线程中的至少一个之前没有进入其同步区域,您将体验到您正在描述的这种实时锁定输出。

似乎正在发生的情况是,如果两个使用者线程都先命中同步块,它们将相互进行乒乓球,等待通知wait。当线程被阻塞时,JVM可能会将等待优先级的线程分配给监视器。

林烨华
2023-03-14

在不深入研究您的代码的情况下,我可以看到您正在使用单个条件变量来实现具有一个生产者和多个使用者的队列。这是一个麻烦的秘诀:如果只有一个条件变量,那么当一个消费者调用 notify() 时,就无法知道它是否会唤醒生产者或唤醒另一个消费者。

有两种方法可以摆脱这个陷阱:最简单的是始终使用< code>notifyAll()。

另一种方法是停止使用synchronizedwait() ,而是使用java.util.concurrent.locks中的工具。

单个 ReentrantLock 对象可以为您提供两个(或更多)条件变量。一个专用于生产者通知消费者,另一个专门用于消费者通知生产者。

注意:当您切换到使用 ReentrantLocks 时,名称会发生变化:o.wait() 变为 c.await(),o.notify() 变为 c.signal()。

 类似资料:
  • 问题内容: 为什么线程不等待?线程启动,然后进入等待池,但是在那一刻之后它将继续执行。 问题答案: 您正在线程对象本身上进行同步,这是错误的用法。即将发生的事情是,即将死去的执行线程总是调用其对象: 依赖于this。因此,很清楚为什么在其中有或没有自己的情况下都会得到相同的行为。 解决方案:使用单独的对象进行线程协调;这是标准做法。

  • 本文向大家介绍Java多线程基础 线程的等待与唤醒(wait、notify、notifyAll),包括了Java多线程基础 线程的等待与唤醒(wait、notify、notifyAll)的使用技巧和注意事项,需要的朋友参考一下 本篇我们来研究一下 wait() notify() notifyAll() 。 DEMO1: wait() 与 notify() DEMO1 输出: 注意: 使用 wait

  • 我做了几个线程转储,发现有16个线程在等待同一个锁,例如: “__ejb-thread-pool1”守护进程prio=6 tid=0x39657c00 nid=0x1c08在条件[0x3297f000]java.lang.thread.state:waiting(parking)在sun.misc.unsafe.park(本机方法)-在java.util.concurrent.locks.lock

  • 我有一个应用程序,通过点击按钮(该数字被定义)用户创建任务(可调用)做一些计算。我希望任务完成时能够做出反应。使用Future.get()阻止应用程序。有什么方法可以在Callable返回结果时做出反应吗?

  • 我对Java线程和并发有点陌生。我读过关于同步和锁定块的书。它们让其他线程等待第一个线程完成其工作。 我只想知道一种方式,如果线程a正在执行它的执行,那么线程B不应该等待并跳过共享代码块的执行。

  • 两个线程在同一个监视器上等待,例如,如果一个线程调用等待锁,而另一个获取监视器的线程在通知第一个线程之前也调用等待。现在两个线程都在等待,但是没有人得到通知。我该怎么称呼这种情况?这能叫僵局吗? 编辑:假设只有这两个线程,并且无法从其他地方通知它们 更新:我刚刚创建了我在问题中描述的情况。当转换器线程在侦听器线程之前启动时,以下代码大部分时间都正常工作。然而,当我在changer之前启动liste