多个线程获取同一个监视器?

时间:2013-07-04 07:22:10

标签: java multithreading thread-safety thread-dump

问题在于讨论“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在锁定&lt; 0xce1fb810&gt;时被阻止。在等待相同锁定时,我的线程-14处于阻塞状态&lt; 0xce1fb810&gt ;.

我们没有遇到任何线程死锁问题。请注意,在任何时间点锁定在锁(0xce1fb810)上的线程随后将释放它。但是,一些其他线程在获取相同的锁定对象后获得BLOCKED。根据上面提到的讨论(sample code提供的&amp; Gray),可能是因为在同步块中调用了wait()。但是,我们检查了我们的代码,我们没有看到在synchronized块中调用wait()。在我们的例子中,它是Linked List的内部实现,而Linked List又有一个实现迭代器的内部类。迭代器实现的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的副本关闭。如上所述,行为类似,但我猜根本原因可能不是。

思想??

4 个答案:

答案 0 :(得分:1)

你不应该如此努力地击中一个锁,我会恢复你的程序,以便通常只有一个线程访问锁。拥有如此沉重的锁争用表明你不应该首先拥有这么多线程因为你没有有效地使用它们而你最好用更少的线程,可能只有一个(因为一个线程不需要锁)< / p>

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

BTW你有多少核心?

答案 1 :(得分:1)

您在Peter的回答下面的评论中提供了重要信息:您的代码通知已注册的听众。这意味着在持有锁的情况下控制外来代码,这是一种已知的不良做法,如Effective Java, Item 67中所述。

重做你的代码,这样你就可以在持有锁的同时首先制作一个监听器列表的安全副本,然后释放锁,然后再调用外来代码。

答案 2 :(得分:1)

  

线程转储表明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()的线程正在等待指定的线程终止。

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

答案 3 :(得分:1)

假设您正在使用OpenJDK或Oracle的HotSpot,我觉得您遇到了this cosmetic bug。症状是多个RUNNABLEBLOCKED线程可能错误地报告已获得相同的监视器,这是不可能的。要了解正在发生的情况,请在- locked <0xce1fb810>状态的任何位置,在- waiting to lock <0xce1fb810>的心理上替换waiting for monitor entry

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