0

问题是围绕“多个 Java 线程看似锁定同一个监视器”的讨论。在我们的应用程序中,我们面临着类似的问题。有时应用程序运行非常缓慢。已捕获多个线程转储。线程转储表明有 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 在锁定 <0xce1fb810> 时被阻塞。MyThread-14 在等待同一个锁 <0xce1fb810> 时处于阻塞状态。

我们肯定不会在这里遇到任何线程死锁问题。请注意,在任何时间点被锁 (0xce1fb810) 阻塞的线程随后都会释放它。但是,其他一些线程在获取相同的锁对象后会被阻塞。根据上面提到的讨论(&格雷提供的示例代码),这可能是因为在同步块中调用了 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())可能导致这种情况?

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

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

想法??

4

4 回答 4

1

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

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

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

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

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

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

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

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

嗯。我的快速回答是,如果线程处于该WAITING状态,则它必须调用wait()某些东西。引用Thread state 的 javadocs

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

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

于 2013-07-10T18:22:12.197 回答
1

Assuming you are using OpenJDK or Oracle's HotSpot, it looks to me like you are running into this cosmetic bug. The symptom is that multiple RUNNABLE or BLOCKED threads may incorrectly report having obtained the same monitor, which is not possible. To understand what is happening, mentally replace - locked <0xce1fb810> by - waiting to lock <0xce1fb810> wherever the thread header is in the waiting for monitor entry state.

(Multiple WAITING threads may report having the lock, it means that they have successfully acquired the lock but then gave it up to enter the waiting state and will attempt to reacquire on exiting the waiting state.)

于 2014-04-07T10:03:29.957 回答
1

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

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

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

于 2013-07-04T07:28:42.387 回答
1

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

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

于 2013-07-04T08:08:37.400 回答