为什么在字段变量上同步并在同步块内递增它会导致打印乱序?

Why synchronizing on the field variable and incrementing it inside synchronized block results in print out of order?

我有一个简单的代码片段

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 块中的所有内容都不应该被正确锁定,无论这个对象是什么,只要它是所有线程之间共享的单个对象?

因为当你做 ++itemCount 时,它等同于:

int old = itemCount.intValue();
itemCount = Integer.valueOf(old + 1);

这改变了锁正在使用的对象,因此同步不再正确。这发生在像 IntegerLong.

这样的原始包装器 类 上

如果将 Integer 替换为可以递增同一对象的 AtomicInteger,您会看到不同之处:

private AtomicInteger itemCount = new AtomicInteger();

public void incrementAndPrint() {
    synchronized(itemCount) { 
        System.out.println(itemCount.getAndIncrement() + " ");
    }
}

Integer 在 Java 中是不可变的。当您调用 ++itemCount 时,您实际上执行了三个操作:首先,Integer 被拆箱为值为 Integerint。然后,这个原语 int 递增,最后递增的 int 被自动装箱回 Integer。所以实际上,您最终会有 不同的 Integer 个实例。由于你在不同的实例上同步,所以同步是没有意义的,你看到的是乱序打印。