问题描述
public class Itemmanager {
private Integer itemCount = 0;
public void incrementAndPrint() {
synchronized(this) {
System.out.println(++itemCount + " ");
}
}
public static void main(String[] args) {
Itemmanager manager = new Itemmanager();
ExecutorService executor = Executors.newFixedThreadPool(20);
for (int i = 0; i < 10; i++) {
executor.submit(manager::incrementAndPrint);
}
executor.shutdown();
}
}
按预期产生 1 2 3 4 5 6 7 8 9 10 。我还可以使用 Object 实例创建另一个字段并锁定它
private Integer itemCount = 0;
private Object lock = new Object();
public void incrementAndPrint() {
synchronized(lock) {
System.out.println(++itemCount + " ");
}
}
它也会如预期的那样产生 1 2 3 4 5 6 7 8 9 10。
但是,如果我尝试锁定要递增和打印的同一个对象
private Integer itemCount = 0;
public void incrementAndPrint() {
synchronized(itemCount) {
System.out.println(++itemCount + " ");
}
}
操作将保持原子性,但结果是乱序的:2 1 3 4 5 6 7 8 9 10。
我知道 synchronized(this)
或同步整个方法将解决我的所有问题。我只是不明白为什么我可以锁定一个字段 (Object lock
),但不能锁定另一个字段 (Integer itemCount
)?不应该正确锁定 synchronized
块内的所有内容,不管这个对象是什么,只要它是所有线程之间共享的单个对象?
解决方法
Java 中的
Integer
是不可变的。当您调用 ++itemCount
时,实际上是在执行三个操作:首先,将 Integer
拆箱为 int
,其值为 Integer
。然后,这个原语 int
递增,最后递增的 int
被自动装箱回 Integer
。所以实际上,您最终会拥有不同 Integer
实例。因为你在不同的实例上同步,所以同步没有意义,你会看到乱序打印。
因为当你做++itemCount
时,它等价于:
int old = itemCount.intValue();
itemCount = Integer.valueOf(old + 1);
这改变了锁正在使用的对象,因此同步不再正确。对于 Integer
和 Long
等原始包装类会发生这种情况。
如果将 Integer
替换为可以递增相同对象的 AtomicInteger
,您可以看到不同之处:
private AtomicInteger itemCount = new AtomicInteger();
public void incrementAndPrint() {
synchronized(itemCount) {
System.out.println(itemCount.getAndIncrement() + " ");
}
}