16

在多线程系统的队列实现方面,我遇到了一些惊喜。这是:-

场景:- 1 个生产者,1 个消费者:- 生产者将一个整数放入队列中。消费者只需将其从队列中删除。

队列的底层数据结构:- TreeSet(我从没想过会用到)、LinkedList、LinkedBlockingQueue(大小不定)

代码:- TreeSet 作为队列:-

while (i < 2000000) {
        synchronized (objQueue) {

            if (!(objQueue.size() > 0)) {
                try {
                    objQueue.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            Integer x = objQueue.first();
            if (x != null) {
                objQueue.remove(x);
                ++i;
            }
        }
    }

编辑:-

      while (i < 2000000) {
        synchronized (objQueue) {
            objQueue.add(i);
            ++i;
            objQueue.notify();
        }
    }

对于 LinkedBlockingQueue:-

     while (i < 2000000){
        try {
            objQueue.put(i);
            ++i;
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            Thread.currentThread().interrupt();
        }
    }

      while (i < 2000000) {
        try {
            objQueue.take();
            ++i;

        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            Thread.currentThread().interrupt();
        }
    }

对于 LinkedList :- 与同步的类似代码。

问题:-

1) 当我通过 Visual VM 测量性能时,我观察到对于生产者代码,TreeSet 的性能优于 LinkedBlockingQueue 和 LinkedList,即使它需要 O(log n) 时间,但在 Linked 结构中创建对象是一个很大的开销. 为什么理论与实践大相径庭?为什么在队列实现中我们更喜欢链接的数组结构而不是树结构?

2) synchronized 与 ReeentrantLock 相比明显胜出,因为 TreeSet 的性能优于 LinkedList,而 LinkedList 的性能优于 LinkedBlockingQueue。我希望我可以附上 Visual VM 结果。这篇文章没有投票,http://www.ibm.com/developerworks/java/library/j-jtp10264/index.html

操作在

Dell Vostro 1015,core 2 duo 2.10,2GB Ram,32 位操作系统和

JVM:Java HotSpot(TM) 客户端 VM(20.1-b02,混合模式) Java:版本 1.6.0_26,供应商 Sun Microsystems Inc.

4

3 回答 3

22

1.如果您需要 实现一个遍历链表的线程,锁定下一个节点然后解锁当前节点,ReentrantLock可能更适合使用。

2. Synchronized关键字适用于锁粗化等情况,提供自适应旋转、偏向锁和通过逃逸分析消除锁的可能性。目前没有为 ReentrantLock 实现这些优化。

有关适当的性能比较,请参见:

https://blogs.oracle.com/dave/javautilconcurrent-reentrantlock-vs-synchronized-which-should-you-use

于 2012-07-22T13:16:05.030 回答
5
  1. 因为您的基准测试存在缺陷:在实际用例中,从队列中生成和使用元素所花费的时间比在队列中添加和删除元素所花费的时间重要得多。所以队列的原始性能并不那么重要。顺便说一句,代码只显示了如何从第一个队列实现中获取元素,而不是如何添加它们。此外,适当结构的选择不是基于性能,而是基于行为。如果你想要一些并发的东西,你选择一个阻塞队列,因为它是为你实现的,并且没有像你的代码那样的错误。如果你想要 FIFO(这通常是你想要的),你不会选择 TreeSet。

  2. 如果你想比较 synchronized 和 ReentrantLock,你不应该对一个数据结构使用一个数据结构,而对另一个数据结构使用另一个数据结构。ReentrantLock 过去更快,但现在它们处于同一水平(如果我相信 Brian Goetz 在 JCIP 中所说的话)。无论如何,出于安全/功能的原因,我会选择其中一个。不是出于性能原因。

于 2012-07-22T13:25:15.257 回答
0

我想我知道问题出在哪里。

从 Java 1.6 开始,如果在单个线程中使用同步块中的锁,它就不会被锁住。因此,在获取锁时不会阻塞代码的执行。这种优化是有意义的——一个线程,不与另一个线程交互——什么都不做。

有两个主要场景需要测试:

  1. 在单线程中执行的测试代码
  2. 多线程

您的测试在一个线程中执行,我猜伪锁不会像互斥锁一样被优化。我猜他们不会被 JIT 优化。

多线程的第二种选择对我来说更有意义,其中ReentrantLock是赢家,因为它在获取锁时不会阻塞所有线程。

于 2020-04-06T22:06:36.297 回答