2011-08-01 43 views
8

如果我有一个变量,多个线程读取并且只有一个线程写入,那么是否需要锁定该变量?如果一个线程尝试读取并且另一个线程尝试同时写入,它会崩溃吗?Java中并发读取/写入变量

回答

7

并发担忧并不崩溃,但你看到的版本的数据。

  • 如果共享变量原子写的,这是可能的,因为当你认为你的(作家)线程已经更新了一个变量(读者)线程读取一个不完整的数值。您可以使用易变关键字来防止读者线程在这种情况下读取过时的值。

  • 如果写入操作不是原子的(例如,如果它是某种组合对象,并且您一次只写一些数据,而其他线程理论上可以读取它),那么您的担心也会有些读者线程可能会看到处于不一致状态的变量。您可以通过在写入时缓存对变量的访问(缓慢)或确保您正在以原子方式写入来防止这种情况。

3

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

+1

我不知道,双打和多头可以处于不一致的状态可以看出。那么这是否也适用于64位系统上的(64位)引用,并且意味着您可以获得无效的引用? –

+0

由于解锁写入导致的不一致的长整数和双整数通常被称为“长期撕裂”。在64位jvm中由64位指针支持的对象引用不受此问题的影响。也就是说,对象引用的“写入”总是原子的。 –

+4

易变“长”和“双”值**是原子**,与非易失性“长”和“双”值(可能不是原子)相反。即使非易失性,其他原始值也是原子。 –

4

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

如果你写一个领域,如果没有某种形式的同步,从其他地方读它,你的程序可以看到不一致的状态,很可能是错误的。你的程序不会崩溃,但可以看到旧的或新的或者(在长时间和双打的情况下)半个新旧数据。

虽然我说“某种形式的同步”,但我更确切地说是指在写入和读取位置之间创建“发生之前”关系(又名存储障碍)的东西。同步或java.util.concurrent.lock类是创建此类事物最明显的方式,但所有并发集合通常也提供类似的保证(检查javadoc以确保)。例如,执行put和take一个并发队列将创建一个before-before关系。

标记字段挥发性阻止你看到不一致的引用(长撕裂),并保证所有的线程将“看”的写操作。但是,易失性字段写入/读取不能与更大原子单元中的其他操作组合。 Atomic类处理常见的组合操作,如比较和设置或读取和增量。同步或其他java.util.concurrent同步器(CyclicBarrier等)或锁应该用于更大的排他性领域。

从简单的是开车的,也有更多的“不,如果你真的知道你在做什么”的情况。举两个例子:

1)一个字段是最后的也是唯一的建设过程中写入的特殊情况。其中一个例子是,当您填充预先计算的缓存时(想到一个Map,其中键是众所周知的值,值是预先计算的派生值)。如果在构建之前将其构建到字段中,并且该字段是最终的,并且以后再也不写入,则构造函数的结尾将执行“最终字段冻结”,并且后续读取不需要同步。

2)被覆盖在有效的Java的“活泼单个检查”图案的情况下。规范示例在java.lang.String中。hashCode()方法。首次调用hashCode()并将其缓存到未同步的本地字段中时,字符串的哈希字段将被延迟计算。基本上,多个线程可以竞争来计算这个值并且设置在其他线程上,但是因为它被一个知名的哨兵(0)保护并且总是计算相同的值(所以我们不关心哪个线程“获胜”或者是否多重做),这实际上是保证没问题。

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