在 Integer 上同步未正确锁定
Synchronize on Integer does not lock correctly
我有一些代码使用 synchronized
来保护我递增的计数器 count++
。
我希望我正确地保护了代码部分,因此得到 2_0000_0000
作为结果,因为在执行它多次后,这将是 count
的正确值,多线程。
但是,当 运行 代码时,我得到的值低于预期 2_0000_0000
,好像我的 synchronized
没有正确保护代码部分。
为什么会这样,我哪里做错了?
public class Test {
private static Integer count = 0;
private static void add10K() {
long idx = 0;
while (idx++ < 1_0000_0000) {
synchronized (count){
count += 1;
}
}
}
public static long calc() {
Thread th1 = new Thread(Test::add10K);
Thread th2 = new Thread(Test::add10K);
th1.start();
th2.start();
try {
th1.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
return count;
}
public static void main(String[] args) {
System.out.println(calc());
}
}
问题描述
Javas synchronized
将锁存储在变量后面的实际对象中,而不是变量本身。
因此,当您将不同的对象分配给变量时,您就有了一个新的锁。
现在,当您执行 count++
时,这实际上不会修改 Integer
而是返回一个新的 Integer
对象(class 是 不可变)。所以count
变量被重新赋值。
为了帮助解释我的观点,请考虑以下情况:
Person person = new Person("John");
...
synchronized (person) {
...
}
锁存储在 John 中,而不是变量 person
本身。所以当有人现在这样做时:
person = new Person("Jane");
synchronized
块不再受保护并且可以再次进入,因为Jane还没有锁定.
对象锁定习语
这就是为什么锁应该只放在 final
变量上,以避免这个问题。此外,您应该为此专门提供一个特定的对象。针对您的情况的惯用修复是:
private static final Object lock = new Object();
然后在上面同步:
synchronized (lock) { ... }
其他非常常见的选择是锁定 class 或 this
(对于非静态情况)。例如 synchronized (Test.class)
。不过,拥有一个专门的对象有一些优势。
资源
如果您喜欢书籍,请参阅 Effective Java、第 82 条 解释 私有对象锁习语:
Note that the lock field is declared final. This prevents you from inadvertently changing its contents, which could result in catastrophic unsynchronized access (Item 78). We are applying the advice of Item 17, by minimizing the mutability of the lock field. Lock fields should always be declared final.
另请参阅有关 What is the use of “private final Object” locking in java multithreading?
的 SO 线程
最后 Oracle Secure Coding Standard§Rule 09. Locking (LCK)#LCK00-J. 本身:
One technique for preventing this vulnerability is the private lock object idiom [Bloch 2001]. This idiom uses the intrinsic lock associated with the instance of a private final java.lang.Object declared within the class instead of the intrinsic lock of the object itself. This idiom requires the use of synchronized blocks within the class's methods rather than the use of synchronized methods. Lock contention between the class's methods and those of a hostile class becomes impossible because the hostile class cannot access the private final lock object.
进行以下更改,它应该会起作用。
static final private Object lock = new Object();
private static void add10K() {
long idx = 0;
while (idx++ < 100_000_000) {
synchronized (lock){
count += 1;
}
}
}
此外,如果您的方法不是静态的,您可以像这样在实例上同步。
synchronized (this) {
count += 1;
}
但是您的线程调用以及您对 calc()
的调用都需要更改以引用实例方法。
我有一些代码使用 synchronized
来保护我递增的计数器 count++
。
我希望我正确地保护了代码部分,因此得到 2_0000_0000
作为结果,因为在执行它多次后,这将是 count
的正确值,多线程。
但是,当 运行 代码时,我得到的值低于预期 2_0000_0000
,好像我的 synchronized
没有正确保护代码部分。
为什么会这样,我哪里做错了?
public class Test {
private static Integer count = 0;
private static void add10K() {
long idx = 0;
while (idx++ < 1_0000_0000) {
synchronized (count){
count += 1;
}
}
}
public static long calc() {
Thread th1 = new Thread(Test::add10K);
Thread th2 = new Thread(Test::add10K);
th1.start();
th2.start();
try {
th1.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
return count;
}
public static void main(String[] args) {
System.out.println(calc());
}
}
问题描述
Javas synchronized
将锁存储在变量后面的实际对象中,而不是变量本身。
因此,当您将不同的对象分配给变量时,您就有了一个新的锁。
现在,当您执行 count++
时,这实际上不会修改 Integer
而是返回一个新的 Integer
对象(class 是 不可变)。所以count
变量被重新赋值。
为了帮助解释我的观点,请考虑以下情况:
Person person = new Person("John");
...
synchronized (person) {
...
}
锁存储在 John 中,而不是变量 person
本身。所以当有人现在这样做时:
person = new Person("Jane");
synchronized
块不再受保护并且可以再次进入,因为Jane还没有锁定.
对象锁定习语
这就是为什么锁应该只放在 final
变量上,以避免这个问题。此外,您应该为此专门提供一个特定的对象。针对您的情况的惯用修复是:
private static final Object lock = new Object();
然后在上面同步:
synchronized (lock) { ... }
其他非常常见的选择是锁定 class 或 this
(对于非静态情况)。例如 synchronized (Test.class)
。不过,拥有一个专门的对象有一些优势。
资源
如果您喜欢书籍,请参阅 Effective Java、第 82 条 解释 私有对象锁习语:
Note that the lock field is declared final. This prevents you from inadvertently changing its contents, which could result in catastrophic unsynchronized access (Item 78). We are applying the advice of Item 17, by minimizing the mutability of the lock field. Lock fields should always be declared final.
另请参阅有关 What is the use of “private final Object” locking in java multithreading?
的 SO 线程最后 Oracle Secure Coding Standard§Rule 09. Locking (LCK)#LCK00-J. 本身:
One technique for preventing this vulnerability is the private lock object idiom [Bloch 2001]. This idiom uses the intrinsic lock associated with the instance of a private final java.lang.Object declared within the class instead of the intrinsic lock of the object itself. This idiom requires the use of synchronized blocks within the class's methods rather than the use of synchronized methods. Lock contention between the class's methods and those of a hostile class becomes impossible because the hostile class cannot access the private final lock object.
进行以下更改,它应该会起作用。
static final private Object lock = new Object();
private static void add10K() {
long idx = 0;
while (idx++ < 100_000_000) {
synchronized (lock){
count += 1;
}
}
}
此外,如果您的方法不是静态的,您可以像这样在实例上同步。
synchronized (this) {
count += 1;
}
但是您的线程调用以及您对 calc()
的调用都需要更改以引用实例方法。