为什么 Java 代码块在使用 synchronized 关键字时比不使用它更快?

Why is a block of Java code faster when using synchronized keyword, rather than without using it?

我正在实现一个线程安全的有界阻塞队列。我可以想到两种方式来设计它。

方法 1:

class BoundedBlockingQueue {
    int capacity;
    Queue<Integer> queue;

    public BoundedBlockingQueue(int capacity) {
        this.capacity = capacity;
        this.queue = new LinkedList();
    }

    public void enqueue(int element) throws InterruptedException {
        while(queue.size() == capacity);
        queue.add(element);
    }

    public int dequeue() throws InterruptedException {
        while(queue.size() == 0);
        return queue.remove();
    }

    public int size() {
        return queue.size();
    }
}

在这里,在排队时,进程将继续循环 while 循环(注意紧随 while 条件之后的立即 ;)并且仅当 queue.size() 变得小于容量时才继续前进。当 queue.size() 等于 0.

时,出队也有类似的逻辑

设计相同事物的第二种方法是使用 synchronized 关键字,如下所示:

方法 2:

class BoundedBlockingQueue {
    int capacity;
    Queue<Integer> queue;

    public BoundedBlockingQueue(int capacity) {
        this.capacity = capacity;
        this.queue = new LinkedList();
    }

    public void enqueue(int element) throws InterruptedException {
        synchronized(queue){
            while(queue.size() == capacity) queue.wait();
            queue.add(element);
            queue.notifyAll();
        }
    }

    public int dequeue() throws InterruptedException {
        synchronized(queue){
            while(queue.size() == 0) queue.wait();
            int val = queue.remove();
            queue.notifyAll();
            return val;
        }
    }

    public int size() {
        return queue.size();
    }
}

在这里,我们让进程等待相同的情况,只有在另一个进程通知它这样做时它才会继续。唯一的区别是我们在方法 2 中使用了 synchronized 关键字,但在方法 1 中我们没有使用。

观察到方法 1 比方法 2 占用更多的运行时间。为什么会这样?这两种方法的底层逻辑不完全一样吗?与方法 2 相比,为什么方法 1 需要更多的运行时间?

非常感谢任何帮助。

您的第一个方法不是线程安全的但是有一个错误(这些方法可能会看到未初始化的值),因为您的字段是not final。我只能强烈建议您在尝试实现自己的线程安全之前先阅读规范类。

可能 capacity 在调用 enqueue 时仍被初始化为默认值 0,这就是您的代码超时的原因,谁知道呢。

第二点,忙等待通常被认为是一件非常糟糕的事情。另外,您不能保证其他线程在调用 size() 时获得正确的值。他们可能会看到过时的值并尝试插入或删除项目,即使队列已满或为空。

如果您想实现线程安全的类队列结构,请查看 Lock and Condition

第三点是,你应该使用 Thread.onSpinWait.

而不是使用空的 while 结构

我也不确定你的第二种方法是否正确,因为 queue 不是 final 并且在调用 synchronized(queue) 时可能是 null。但我不确定这是否真的可以在这里发生。但是无论如何 queue 没有理由不是 final

以上代码存在竞争条件,因此第一个示例不提供与第二个示例相同的同步保证。

  • 想象线程数 1 和 2 运行 在行 while(queue.size() == 0); 上。
  • 线程号 3 线程调用 enqueue()
  • 线程 1 和 2(同时)观察到 while 循环的条件不成立,因为队列大小现在为 1,并且两者同时尝试调用 queue.remove()。这里其中一个线程将抛出异常(即使 LinkedList 是线程安全的)。我假设这不是您要达成的合同。

在第一个解决方案中,您应用了自旋锁(忙等待)来解决同步问题。仅在特定情况下,这可能比 OS 依赖同步构造(信号量、互斥...)更有效。有关此内容的更多信息 here