Synchronize on Integer不能正确锁定



我有一些代码使用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());
}
}

问题描述

javasynchronized将锁存储在变量后面的实际对象中,而不是变量本身。

所以当你给变量赋值一个不同的对象时,你就有了一个新的锁。

现在,当您执行count++时,这实际上不会修改Integer,而是返回一个新的Integer对象(类是不可变)(. 因此,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) { ... }

另一个非常常见的选择是锁定类或this(用于非静态情况)。比如synchronized (Test.class)。不过,拥有一个专用对象也有一些好处。


如果你喜欢书,参考Effective Java第82项解释私有对象锁习惯用法:

注意,lock字段被声明为final。这可以防止您无意中更改其内容,这可能导致灾难性的不同步访问(第78项)。我们通过最小化锁字段的可变性来应用第17项的建议。锁字段应该总是声明为final。

也可以参阅SO线程关于"私有最终对象"锁定在java多线程中的用途是什么?

最后是Oracle安全编码标准§规则09。锁(LCK) # LCK00-J。本身:

防止此漏洞的一种技术是私有锁对象习惯用法[Bloch 2001]。这种习惯用法使用与类中声明的私有final java.lang.Object实例相关联的内在锁,而不是对象本身的内在锁。这种习惯用法要求在类的方法中使用同步块,而不是使用同步方法。类的方法和敌对类的方法之间的锁争用变得不可能,因为敌对类不能访问私有的final锁对象。

做以下更改,它应该可以工作了。

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()的调用来引用实例方法。

相关内容

  • 没有找到相关文章

最新更新