并发地读/写java中的一个变量



如果我有一个变量,多个线程读,只有一个线程写,我需要有一个锁围绕该变量吗?如果一个线程试图读,另一个线程同时试图写,它会崩溃吗?

并发性问题不是崩溃,而是您看到的数据的版本。

  • 如果共享变量是自动写入的,那么当您认为(写入器)线程已经更新了变量时,一个(读取器)线程可能会读取到一个过时的值。在这种情况下,您可以使用volatile关键字来防止读取线程读取过时的值。

  • 如果写操作不是原子的(例如,如果它是某种复合对象,并且您一次写入它的位,而其他线程理论上可能正在读取它),那么您还会担心一些读取线程可能会看到变量处于不一致的状态。要防止这种情况,可以在写入变量时(缓慢)锁定对变量的访问,或者确保自动写入。

写入一些类型的字段是原子的,但是没有发生在之前的关系确保了正确的内存顺序(除非你使用volatile);

简单的答案是肯定的,您需要同步。

如果你曾经写一个字段,并从其他地方读它没有某种形式的同步,你的程序可以看到不一致的状态,很可能是错误的。您的程序不会崩溃,但可以看到旧数据或新数据,或者(在long和double的情况下)一半旧数据一半新数据。

当我说"某种形式的同步"时,我更准确地指的是在写和读位置之间创建"happens-before"关系(又名内存屏障)的东西。Synchronization或java.util.concurrent.lock类是创建这种东西的最明显的方法,但是所有并发集合通常也提供类似的保证(请检查javadoc以确定)。例如,对并发队列执行put和取操作将创建happens-before关系。

将字段标记为volatile可以防止您看到不一致的引用(长撕裂),并保证所有线程都将"看到"写入。但是volatile字段写/读不能在更大的原子单元中与其他操作组合。原子类处理常见的组合操作,如比较-设置或读取-自增。同步或其他java.util.concurrent同步器(CyclicBarrier等)或锁应该用于更大的独占区域。

除了简单的"是"之外,还有更多的情况是"不,如果你真的知道你在做什么"。两个例子:

1)仅在构造过程中写入的final字段的特殊情况。其中一个例子是当您填充一个预先计算的缓存时(想象一个Map,其中键是众所周知的值,而值是预先计算的派生值)。如果你在构造之前在一个字段中构建了它,并且该字段是final的,并且你以后从未对它进行写入,那么构造函数的末尾执行"final field freeze",随后的读取不需要同步。

2) Effective Java中提到的"有效的单检查"模式。典型的例子是java.lang.String.hashCode()。String有一个散列字段,它在第一次调用hashCode()时惰性计算,并缓存在本地字段中,这是不同步的。基本上,多个线程可能会竞相计算这个值,并在其他线程上进行设置,但由于它由一个众所周知的哨兵(0)保护,并且总是计算相同的值(所以我们不关心哪个线程"获胜",或者多个线程是否获胜),因此这实际上是可以保证的。

更长的参考(由我写的):http://refcardz.dzone.com/refcardz/core-java-concurrency

请注意volatile不是原子性的,这意味着使用64位的double和long可以在不一致的状态下读取,其中32位是旧值,32位是新值。此外,volatile数组不会使数组条目变为volatile。强烈建议使用java.util.concurrent中的类

最新更新