为何不同Counter2实例的线程竟共用同一锁?Java并发锁问题咨询
问题:不同Counter实例的线程为何似乎竞争同一锁?
代码片段
class Counter2 implements Runnable{ private int value = 0; private Integer lock = 0; public void increment(){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } value++; } public void decrement(){ value--; } public int getValue() { return value; } @Override public void run() { synchronized(lock) { this.increment(); System.out.println(String.format("%s incremented value, value = %s", Thread.currentThread().getName(), this.value)); this.decrement(); System.out.println(String.format("%s decremented value, value = %s", Thread.currentThread().getName(), this.value)); } } } public class Part8_synchronized_keyword_1 { public static void main(String[] args) { Counter2 counter = new Counter2(); new Thread(counter,"Thread1").start(); new Thread(counter,"Thread2").start(); new Thread(counter,"Thread3").start(); new Thread(counter,"Thread4").start(); Counter2 counterB = new Counter2(); new Thread(counterB, "ThreadB").start(); } }
运行输出
Thread1 incremented value, value = 1 Thread1 decremented value, value = 0 ThreadB incremented value, value = 1 ThreadB decremented value, value = 0 Thread4 incremented value, value = 1 Thread4 decremented value, value = 0 Thread3 incremented value, value = 1 Thread3 decremented value, value = 0 Thread2 incremented value, value = 1 Thread2 decremented value, value = 0
问题
为何ThreadB似乎与Thread1至Thread4在竞争同一锁?按我的理解,counter和counterB的lock对象应该是不同的。
解答
核心原因是Integer的自动装箱缓存机制:
- 当你写
private Integer lock = 0;时,Java会自动把int类型的0装箱成Integer对象。 - Java为了优化性能,会缓存
-128到127之间的Integer对象,也就是说所有指向这个范围数值的Integer引用,都会复用同一个对象实例。 - 所以counter和counterB的lock变量,其实引用的是同一个Integer对象,自然会导致所有线程都在竞争同一把锁,出现串行执行的情况。
修复方案
可以通过以下几种方式避免这个问题:
- 使用
new Integer(0)创建锁对象,绕过缓存:private Integer lock = new Integer(0); - 改用Object作为锁对象(更推荐,因为Object不会有缓存问题):
private Object lock = new Object(); - 直接用当前对象实例
this作为锁(如果不需要区分锁粒度的话):synchronized(this) { // ... }
内容的提问来源于stack exchange,提问作者H-Bar
相关产品推荐
相关产品推荐

