为什么 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
我正在实现一个线程安全的有界阻塞队列。我可以想到两种方式来设计它。
方法 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