为什么在字段变量上同步并在同步块内递增它会导致打印乱序?
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);
这改变了锁正在使用的对象,因此同步不再正确。这发生在像 Integer
和 Long
.
这样的原始包装器 类 上
如果将 Integer
替换为可以递增同一对象的 AtomicInteger
,您会看到不同之处:
private AtomicInteger itemCount = new AtomicInteger();
public void incrementAndPrint() {
synchronized(itemCount) {
System.out.println(itemCount.getAndIncrement() + " ");
}
}
Integer
在 Java 中是不可变的。当您调用 ++itemCount
时,您实际上执行了三个操作:首先,Integer
被拆箱为值为 Integer
的 int
。然后,这个原语 int
递增,最后递增的 int
被自动装箱回 Integer
。所以实际上,您最终会有 不同的 Integer
个实例。由于你在不同的实例上同步,所以同步是没有意义的,你看到的是乱序打印。
我有一个简单的代码片段
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);
这改变了锁正在使用的对象,因此同步不再正确。这发生在像 Integer
和 Long
.
如果将 Integer
替换为可以递增同一对象的 AtomicInteger
,您会看到不同之处:
private AtomicInteger itemCount = new AtomicInteger();
public void incrementAndPrint() {
synchronized(itemCount) {
System.out.println(itemCount.getAndIncrement() + " ");
}
}
Integer
在 Java 中是不可变的。当您调用 ++itemCount
时,您实际上执行了三个操作:首先,Integer
被拆箱为值为 Integer
的 int
。然后,这个原语 int
递增,最后递增的 int
被自动装箱回 Integer
。所以实际上,您最终会有 不同的 Integer
个实例。由于你在不同的实例上同步,所以同步是没有意义的,你看到的是乱序打印。