使一个POJO线程安全

3

这是一个类:

@NotThreadSafe
public class MutableInteger {
    private int value;
    public int get() { return value;}
    public void set(int value) { this.value = value;} 
}

这是我想出的后置条件: get() 返回的值等于 set() 设置的值或 0。
很容易看出,上述后置条件并不总是成立。以两个线程 A 和 B 为例。假设 A 将 value 设置为 5,然后 B 将其设置为 8。在线程 A 中执行 get() 会返回 8,但应该返回 5。这是一个简单的竞争条件。
如何使这个类变得线程安全?在书籍《Java: Concurrency in Practice》中,作者对同一对象上的 value 和两个方法进行了保护。我无法理解这如何有助于解决竞争条件。首先,set() 不是一个复合操作。那么,我们为什么需要对其进行同步?即使这样做,竞争条件也不会消失。一旦线程从 set() 方法退出并释放锁,另一个线程就可以获取锁并设置新值。在初始线程中执行 get() 将返回新值,违反了后置条件。
(我知道作者是为了可见性而保护 get()),但我不确定它如何消除竞争条件。

1
你所描述的并不是“线程安全”的含义。如果任何一个线程将值设置为8,任何其他线程(或相同线程)在调用get()时都应该得到8!如果你想要每个线程拥有自己的值,你需要使用线程局部变量——请参见ThreadLocal类。 - user85421
天啊,这是多么幼稚的错误啊。我忘记了它是一个单一的对象。真丢人。 - Prashant Pandey
回复:“我不明白这如何有助于解决竞态条件问题。” 使类“线程安全”并不能保证使用它的代码也是“线程安全”的。如果你编写了某个方法 m(),并且该方法所涉及的每个对象都是线程安全的,每个方法都是线程安全的,但这仍然不足以保证方法 m() 是线程安全的。 - Solomon Slow
一个队列应该保证项按照它们进入的顺序出来,而且一个线程安全队列即使在许多线程同时调用其方法时也应该保持这个承诺,但是这个承诺不能帮助调用者控制项进入的顺序。如果你需要项 A 在项 B 之前出现,那么就 的责任去 证明 在某个线程完成向队列中加入项 A 之后,没有线程会 开始 向队列中放入项 B。 - Solomon Slow
1个回答

2
首先,set() 不是一个复合操作。那么,我们为什么需要对它进行同步?
你不是在单独同步 set(),而是将 get() 和 set() 方法与同一对象同步(假设你将这两个方法都设置为同步)。如果你没有这样做,并且 value 变量没有标记为 volatile,那么由于每个线程的缓存,无法保证线程将看到正确的值。(线程 a 可能将其更新为 5,然后线程 b 即使在线程 a 更新后仍可能看到 8。这就是在这种情况下缺乏线程安全性的含义。)
你是正确的,所有引用赋值都是原子的,因此在这种情况下不必担心引用损坏。
即使如此,竞争条件也不会消失。一旦一个线程从 set() 方法退出,锁被释放,另一个线程就可以获取锁并设置新值。
新线程设置新值(或新代码设置新值)在线程安全方面不是问题 - 这是设计和预期的。问题在于结果是否不一致,或者特别是多个线程能否同时查看处于不一致状态的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接