【问题标题】:Multiple threads acquiring the same monitor?多个线程获取同一个监视器?
【发布时间】:2013-07-04 07:22:10
【问题描述】:

问题是围绕讨论“Multiple Java threads seemingly locking same monitor”。在我们的应用程序中,我们面临着类似的问题。有时应用程序运行非常缓慢。已捕获多个线程转储。线程转储表明有 2/3 个线程在同一时间点获取了相同的锁对象并处于 BLOCKED 状态。其他线程(在不同时间点有 10 到 20 个)在等待同一个锁对象时被阻塞。伪线程转储如下所示:

"MyThread-91" prio=3 tid=0x07552800 nid=0xc7 waiting for monitor entry [0xc4dff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.myCompany.abc.util.MySharedLinkedList$MySharedIterator.hasNext(MySharedLinkedList.java:177)
    - locked <0xce1fb810> (a com.myCompany.abc.util.MySharedLinkedList)
    at com.myCompany.abc.util.MyEventProcessor.notifyListeners(MyEventProcessor.java:2644)
    ...............................................................................................

"MyThread-2" prio=3 tid=0x07146400 nid=0x6e waiting for monitor entry [0xc6aef000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.myCompany.abc.util.MySharedLinkedList$MySharedIterator.hasNext(MySharedLinkedList.java:177)
    - locked <0xce1fb810> (a com.myCompany.abc.util.MySharedLinkedList)
    at com.myCompany.abc.util.MyEventProcessor.notifyListeners(MyEventProcessor.java:2644)
    ................................................................................................

"MyThread-14" prio=3 tid=0x074b9400 nid=0x7a waiting for monitor entry [0xc64ef000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.myCompany.abc.util.MySharedLinkedList$MySharedIterator.next(MySharedLinkedList.java:194)
    - waiting to lock <0xce1fb810> (a com.myCompany.abc.util.MySharedLinkedList)
    at com.myCompany.abc.util.MyEventProcessor.notifyListeners(MyEventProcessor.java:2646)
    ................................................................................................

MyThread-91 和 MyThread-2 在锁定 时被阻塞。 MyThread-14 在等待同一个锁 时处于 BLOCKED 状态。

我们肯定没有遇到任何线程死锁问题。请注意,在任何时间点被锁 (0xce1fb810) 阻塞的线程随后都会释放它。但是,其他一些线程在获取相同的锁对象后会被阻塞。根据上面提到的讨论(&sample codeGray 提供),这可能是因为在同步块中调用了wait()。但是,我们检查了我们的代码,我们没有看到在同步块中调用了任何 wait()。在我们的例子中,它是链表的内部实现,而链表又具有实现迭代器的内部类。迭代器实现的 next() 和 hasNext() 锁定外部类的同一个实例,即自定义链表的实例。当多个线程调用 next() 和 hasNext() 时,它们在“获取”同一个锁之后进入 BLOCKED 状态。

伪代码如下:

public final class MySharedLinkedList<E> implements Collection<E> {
    /**
     * Represents an entry in the list. 
     */
    private static final class Entry<E> {
        //Instance variables and methods for Entry goes here.
    }

    /**
     * Non fail-fast implementation of iterator for this list.
     */
    public final class MySharedIterator implements Iterator<E> {

        public boolean hasNext() {
            //Some code goes here.
            synchronized (MySharedLinkedList.this) {
                //Some code goes here.
            }
        }

        public E next() {
            //Some code goes here.
            synchronized (MySharedLinkedList.this) {
                //Some code goes here.
            }
        }
    }

    public synchronized Iterator<E> iterator() {
        //Returns a new instance of the iterator.
    }
}

/**
 * Singleton Instance
 */
public class MyEventProcessor {

    //listeners contains a number of Node objects
    private final SharedLinkedList<Node> listeners = new SharedLinkedList<Node>();

    private void notifyListeners() {

        final SharedLinkedList<ProvAPIEventNode>.SharedIterator iterator = listeners.sharedIterator();
        try {
            while (iterator.hasNext()) {
                final Node node = iterator.next();
                //Lots of other things go here
            } catch (Exception e) {
                //Handle the exception
            }
        }
    }
}

那么,问题是还有什么(除了wait())可能导致这种情况?

This blog 谈到了类似的情况(在“示例 2:当处理性能异常缓慢时”部分下)。但不确定这里是否发生了类似的事情。

不要关闭此线程作为thisthis 的副本。如前所述,行为相似,但我想根本原因可能不是。

想法??

【问题讨论】:

  • 您没有检查从代码块中调用的每个方法的代码,对吗?等待一定发生在某处
  • 如果您在同步块内的同一监视器上调用wait()(您确实这样做了,对吗?),这会自动释放锁,因此这不是原因。缺少对 notifyAll() 的调用?另外,一些代码呢?这些是什么显示器?仅同步块、可重入锁、信号量、条件...?
  • 不,令人惊讶的是我们的代码没有任何等待/通知调用。它是链表的内部实现,而链表又具有实现迭代器的内部类。迭代器实现的 next() 和 hasNext() 锁定外部类的同一个实例,即自定义链表。当多个线程调用 next() 和 hasNext() 时,它们在“获取”同一个锁后进入 BLOCKED 状态。

标签: java multithreading thread-safety thread-dump


【解决方案1】:

你不应该这么用力地敲一个锁,我会重新构建你的程序,这样通常不会有超过一个线程访问锁。有如此严重的锁争用表明你一开始就不应该有这么多线程,因为你没有有效地使用它们,你最好用更少的线程,可能只有一个(因为一个线程不需要锁)

我建议您从一个线程开始,并且仅在您知道它有助于提高性能时才添加线程。不要假设更多线程会有所帮助,因为您可以拥有像您这样的代码,其中必须使用锁的开销超过您可能获得的任何收益。

顺便说一句,你有多少个内核?

【讨论】:

  • 所讨论的线程主要是工作线程,通常在执行完成后通知注册的侦听器。这就是线程进入 BLOCKED 状态的时候。这是过去几年的行为。通常,我们的客户在其生产中使用不少于 8 个内核。
【解决方案2】:

您在彼得回答下方的评论中提供了重要信息:您的代码会通知注册的听众。这意味着它在持有锁的同时将控制权交给外来代码,这是一种已知的不良做法,如 Effective Java, Item 67 中所述。

重新编写您的代码,以便您首先在持有锁的情况下制作监听器列表的安全副本,然后释放锁,然后才调用外来代码。

【讨论】:

  • 没有。我们没有这样做:“在同步区域内,不要调用旨在被覆盖的方法,或者由客户端以函数对象的形式提供的方法”。我们没有将对象传递给客户端。我将提供伪代码。
  • 马尔科,我迟到了。但是,在更新的问题中包含了伪代码。
【解决方案3】:

线程转储表明有 2/3 个线程在同一时间点获取了同一个锁对象,并且处于 BLOCKED 状态。

这意味着它们已准备好运行但被阻塞等待获得锁。这是锁争用,正如@Peter 所提到的,您应该减少同步的代码部分或锁定不同的对象。例如,请务必将日志记录或其他 IO 移到 synchronized 块之外。

其他线程(在不同时间点有 10 到 20 个)在等待同一个锁对象时被阻塞。

这意味着他们正在等待其他线程通知对象。这不是问题。

但是,其他一些线程在获取相同的锁对象后被阻塞了。

这在技术上是不可能的。 BLOCKED 表示他们正在尝试锁定对象。一次只有一个线程可以锁定特定对象。所有其他试图锁定它的线程都被 BLOCKED。

other discussion that you reference 中讨论的重要一点是,当您调用synchronized (obj) { obj.wait(); } 时,它会获取锁,然后释放它直到它收到通知(或等待超时或线程被中断)。即使堆栈跟踪显示locked,当wait() 导致线程为WAITING 时,锁也会被释放。

我们检查了我们的代码,我们没有看到任何 wait() 在同步块中被调用...

嗯。我的快速回答是,如果一个线程处于WAITING 状态,它必须在某事上调用wait()。引用javadocs on Thread state

处于等待状态的线程正在等待另一个线程执行特定操作。例如,对对象调用 Object.wait() 的线程正在等待另一个线程对该对象调用 Object.notify() 或 Object.notifyAll()。已调用 Thread.join() 的线程正在等待指定线程终止。

当您访问另一个对象时,会不会有一个内部调用等待?可以等待另一个对象而不是您的列表吗?

【讨论】:

  • 我已经更新了这个问题。有问题的线程不处于 WAITING 状态。它们要么在获得锁时被 BLOCKED,要么在等待锁时被 BLOCKED。我重新访问了代码,并没有在任何地方看到任何 wait() 。如伪堆栈跟踪所示,它从不调用 Object.wait()。如果在某处调用了 wait(),它不应该反映在堆栈跟踪中吗?
  • 我了解,根据 java 文档,线程可能会在调用 Object.wait() 后尝试重新进入同步块/方法时被锁定并获取锁。但是,除了wait()还有什么可能吗?
【解决方案4】:

假设您使用的是 OpenJDK 或 Oracle 的 HotSpot,在我看来您遇到了this cosmetic bug。症状是多个RUNNABLEBLOCKED 线程可能错误地报告已获得相同的监视器,这是不可能的。要了解正在发生的事情,请将- locked &lt;0xce1fb810&gt; 替换为- waiting to lock &lt;0xce1fb810&gt;,只要线程头处于waiting for monitor entry 状态。

(多个WAITING线程可能会报告有锁,这意味着他们已经成功获取了锁但随后放弃了它进入等待状态,并会在退出等待状态时尝试重新获取。)

【讨论】:

  • 我们使用的是 jdk_1.6。这个bug和1.8有关?
  • 我针对 1.8 提交了它,但它也存在于 1.6 和 1.7 中(使用您的 JVM 尝试测试用例)。
猜你喜欢
  • 2012-03-21
  • 2012-07-07
  • 1970-01-01
  • 1970-01-01
  • 2016-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多