对正在修改的静态字段进行同步是否可以确保代码线程安全


class A {
private static BigInteger staticCode = BigInteger.ZERO;
private BigInteger code;
public A() {
synchronized(staticCode) {
staticCode = staticCode.plus(BigInteger.ONE);
code = staticCode;
}
}
}

无论如何,我都不是并发方面的专家。有人能向我解释一下为什么上面提供的类不是线程安全的吗?

哪些情况会导致比赛状况?我的想法是,如果我们创建这个类的10个实例,每个实例都将在不同的staticCode值上同步,这就是为什么它是线程安全的,但我被告知它不是。但为什么呢?

我知道我们可以在.class上同步,它肯定是线程安全的,但我仍然想了解这种特殊情况。

在正在修改的静态字段上进行同步是否可以确保代码线程的安全?

不,因为您正在重新分配它。(*(

重新分配后,您实际上就失去了访问staticCode字段的互斥性。

  • 在分配之前已经在synchronized块等待的任何线程都将继续等待
  • 在重新分配之后但在重新分配线程离开块之前到达synchronized块的任何线程将尝试在staticCode值上同步。

    比没有互斥的事实更微妙的一点是,在同步块结束和下一次执行开始之间,还会丢失之前发生的事情。这意味着您不能保证更新值的可见性,因此您可能会生成具有相同code的多个A实例。

在非最终成员上同步是个坏主意。如果你不想在A.class上同步,你可以定义一个要同步的辅助成员:

class A {
private static final Object lock = new Object();
private static BigInteger staticCode = BigInteger.ZERO;
public A() {
synchronized (lock) {
staticCode = ...
}
}
}

这保留了staticCode的可变性,但允许正确的互斥。

然而,Atomic*类会容易得多,因为您可以避免同步的需要(例如AtomicIntegerAtomicLong,但如果您真的认为您将拥有超过2^63的东西,则可以使用AtomicReference<BigInteger>(:

class A {
private static final Object lock = new Object();
private static AtomicReference<BigInteger> staticCode = new AtomicReference<>(BigInteger.ZERO);
public A() {
BigInteger code;
do {
code = staticCode.get();
} while (!staticCode.compareAndSet(code, code.add(BigInteger.ONE)));
this.code = code;
// Even easier with AtomicInteger/Long:
// this.code = BigInteger.valueOf(staticCode.incrementAndGet());
}
}

(*(但无论如何,不要再考虑同步会自动确保线程安全的概念。首先,您需要准确定义"线程安全"的含义;但是,您需要了解同步实际上为您提供了什么,以便评估这些东西是否满足您的线程安全要求。

我想我在这里缺少的要点是我们在对象上同步,而不是对对象的引用。

考虑一种情况,我在BigInteger.ZERO上同步,然后输入同步块。

当staticCode的值发生变化并变为BigInteger.ONE时,该块仍将继续在BigInteger.ZERO上同步。同时,在我们将BigInteger.NE分配给代码之前,另一个线程已经在BigInteger.ONE上同步。第二个线程可以将staticCode的值提升到2,现在两个线程都在第二次赋值之前,但staticCode的数值是2,因此它们都可以将相同的staticCode值赋值给类的两个不同实例。

最新更新