【问题标题】:Deadlock caused by blocking methods阻塞方法导致的死锁
【发布时间】:2016-01-31 20:59:13
【问题描述】:

假设我们创建了一个运行同步方法的线程。这个方法尝试从一个空的阻塞队列中take()。现在让一个单独的线程尝试将put() 和元素放入阻塞队列,同时在同一个对象上同步。

这会导致死锁:

  • 在将元素添加到队列之前,第一个线程不会释放锁。
  • 第二个线程无法添加元素,直到它可以获取锁为止。

如果这两个动作需要是原子的并在不同的线程上运行,如何在不导致死锁的情况下实现这一点?

我了解take()put() 是线程安全的。我的问题是它们何时被用作必须是原子的更大动作的一部分。

例子:

import java.util.concurrent.*;

public class DeadlockTest {

    String input = "Nothing added yet!";
    LinkedBlockingQueue<String> buffer = new LinkedBlockingQueue<>();

    public synchronized String getFromBuffer() {
        System.out.println("Trying to get input from buffer.");
        try {
            input = buffer.take();
        } catch (InterruptedException ex) {}
        System.out.println("Got:" + input + "\n");
        return input;
    }

    public static void main(String[] args) throws InterruptedException {
        DeadlockTest dl = new DeadlockTest();

        new Thread(() -> {
            dl.getFromBuffer();
        }).start();

        // Give new thread time to run.
        Thread.sleep(500);

        synchronized (dl) {
            String message = "Hello, world!";

            System.out.println("Adding: " + message);
            dl.buffer.put(message);
            System.out.println("Added!\n");

            System.out.println("Message: " + dl.input);
        }
    }
}

【问题讨论】:

  • 嗯,是的,所以即使队列为空,take/pop/whatever 也必须释放锁。
  • 通常,你使用额外的线程来执行阻塞的东西,然后使用一些事件循环读取结果。
  • @MartinJames take()put() 获取和释放的锁对象与同步方法使用的锁对象不同。我无权访问阻塞队列内部的锁。如果我这样做了,我可以将它用作同步块的参数,而不是使方法同步。

标签: java multithreading


【解决方案1】:

假设我们创建了一个运行同步方法的线程。这个方法试图从一个空的阻塞队列中取()。

听起来像是糟糕的设计。从synchronized 方法或synchronized 语句中调用任何阻塞方法通常是错误的。

如果这两个动作需要是原子的并在不同的线程上运行,如何在不导致死锁的情况下实现呢?

嗯,有两种可能:

在一种情况下,两个线程作用于不同的数据。在这种情况下,它们应该使用不同的锁,并且它们根本不会相互干扰。

在另一种情况下,两个线程作用于相同的数据。在这种情况下,它们应该锁定同一个锁,并且一个线程将不得不等待另一个。


也许您误解了阻塞队列的工作原理。如果一个线程正在等待来自阻塞队列的take() 某些东西,那绝不应该阻止另一个线程调用put()。那将与您想要的完全相反。

您想要的(以及您将从 Java 标准库中的任何阻塞队列实现中得到的)是第二个线程中的 put() 操作将唤醒等待 take() 的线程来自队列。

【讨论】:

  • 感谢詹姆斯的反馈!糟糕的设计肯定是问题所在。这就是为什么我要问如何用不同的设计达到类似的效果。我的理解是,“take()”在阻塞时确实会释放锁。但是,这与同步方法使用的锁对象不同。如果我可以访问阻塞队列内部的锁定对象,那么我可以将其用作同步块的参数。不过,这可能也是糟糕的设计!
  • 您想要的原始功能是wait()/notify() 机制。在我对另一个问题的回答中查看produceSomething()consumeSomething() 方法。 stackoverflow.com/questions/26590542/… lock.wait() 调用只允许在 synchronized(lock) 语句中。它暂时解锁锁,等待其他线程通知,然后在返回之前重新锁上锁。
猜你喜欢
  • 2014-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-04
  • 1970-01-01
  • 2021-09-14
  • 1970-01-01
相关资源
最近更新 更多