简体   繁体   English

并发读取/写入java中的变量

[英]Concurrent read/write to a variable in java

If I have a variable from which multiple threads read and only one thread writes, do I need to have a lock around that variable? 如果我有一个变量从多个线程读取并且只有一个线程写入,我是否需要锁定该变量? Would it crash if one thread tries to read and the other thread tries to write at the same time? 如果一个线程试图读取而另一个线程试图同时写入,它会崩溃吗?

The concurrency concern is not crashing, but what version of the data you're seeing. 并发问题不是崩溃,而是你看到的数据版本。

  • if the shared variable is written atomically, it's possible for one (reader) thread to read a stale value when you thought your (writer) thread had updated the variable. 如果共享变量是以原子方式编写的,那么当您认为(编写者)线程更新了变量时,一个(读取器)线程可能会读取过时的值。 You can use volatile keywords to prevent your reader threads from reading a stale value in this situation. 您可以使用volatile关键字来防止读者线程在这种情况下读取过时的值。

  • if the write operation is not atomic (for example if it's a composite object of some kind and you're writing bits of it at a time, while the other threads could theoretically be reading it) then your concern would also be that some reader threads could see the variable in an inconsistent state. 如果写操作不是原子的(例如,如果它是某种类型的复合对象,并且您一次写入它的位,而其他线程理论上可以读取它)那么您的关注点也会是某些读者线程可以看到变量处于不一致状态。 You'd prevent this by locking access to the variable while it was being written (slow) or making sure that you were writing atomically. 您可以通过在变量写入时锁定对变量的访问(缓慢)或确保您以原子方式编写来阻止此操作。

Writes to some types of fields are atomic but without a happens-before relationship that ensures correct memory ordering (unless you use volatile ); 写入某些类型的字段是原子的,但没有 发生在之前的关系,以确保正确的内存排序(除非你使用volatile ); see this page for details. 请参阅此页面了解详情。

The simple answer is yes, you need synchronization. 简单的答案是肯定的,你需要同步。

If you ever write to a field and read it from anywhere else without some form of synchronization, your program can see inconsistent state and is likely wrong. 如果您在没有某种形式的同步的情况下写入某个字段并从其他任何地方读取它,您的程序可能会看到不一致的状态并且可能是错误的。 Your program will not crash but can see either the old or new or (in the case of longs and doubles) half old and half new data. 你的程序不会崩溃,但可以看到旧的或新的或(在长和双的情况下)半旧和半新数据。

When I say "some form of synchronization" though, I more precisely mean something that creates a "happens-before" relationship (aka memory barrier) between the write and read locations. 当我说“某种形式的同步”时,我更准确地说是在写入和读取位置之间创建“先发生过”关系(也称为内存屏障)的东西。 Synchronization or java.util.concurrent.lock classes are the most obvious way to create such a thing, but all of the concurrent collections typically also provide similar guarantees (check the javadoc to be sure). 同步或java.util.concurrent.lock类是创建此类事物最明显的方法,但所有并发集合通常也提供类似的保证(检查javadoc是否确定)。 For example, doing a put and take on a concurrent queue will create a happens-before relationship. 例如,执行put和take并发队列将创建一个before-before关系。

Marking a field as volatile prevents you from seeing inconsistent references (long-tearing) and guarantees that all threads will "see" a write. 将字段标记为volatile可防止您看到不一致的引用(长时间撕裂)并保证所有线程都“看到”写入。 But volatile fields writes/reads cannot be combined with other operations in larger atomic units. 但是,易失性字段写入/读取不能与更大原子单元中的其他操作组合。 The Atomic classes handle common combo ops like compare-and-set or read-and-increment. Atomic类处理常见的组合操作,如比较和设置或读取和增量。 Synchronization or other java.util.concurrent synchronizers (CyclicBarrier, etc) or locks should be used for larger areas of exclusivity. 同步或其他java.util.concurrent同步器(CyclicBarrier等)或锁应该用于更大的排他性区域。

Departing from the simple yes, there are cases that are more "no, if you really know what you're doing". 偏离简单的是,有些情况更“不,如果你真的知道你在做什么”。 Two examples: 两个例子:

1) The special case of a field that is final and written ONLY during construction. 1)场地的特殊情况是最终的并且仅在施工期间书写。 One example of that is when you populate a pre-computed cache (think of a Map where keys are well-known values and values are pre-computed derived values). 其中一个例子是当你填充预先计算的缓存时(想想一个Map,其中键是众所周知的值,值是预先计算的派生值)。 If you build that in a field prior to construction and the field is final and you never write to it later, the end of the constructor performs "final field freeze" and subsequent reads DO NOT need to synchronize. 如果你在构造之前在字段中构建它并且字段是final并且你以后永远不会写它,那么构造函数的结尾执行“final field freeze”并且后续读取不需要同步。

2) The case of the "racy single check" pattern which is covered in Effective Java. 2)Effective Java中涵盖的“racy single check”模式的情况。 The canonical example is in java.lang.String.hashCode(). 规范示例位于java.lang.String.hashCode()中。 String has a hash field that is lazily computed the first time you call hashCode() and cached into the local field, which is NOT synchronized. String有一个哈希字段,在你第一次调用hashCode()并缓存到本地字段时是懒惰计算的,该字段是未同步的。 Basically, multiple threads may race to compute this value and set over other threads, but because it is guarded by a well-known sentinel (0) and always computes the identical value (so we don't care which thread "wins" or whether multiple do), this actually is guaranteed to be ok. 基本上,多个线程可能竞争计算此值并设置其他线程,但因为它由一个众所周知的sentinel(0)保护并始终计算相同的值(所以我们不关心哪个线程“获胜”或是否多个do),这实际上保证是好的。

A longer reference (written by me): http://refcardz.dzone.com/refcardz/core-java-concurrency 更长的参考(由我编写): http//refcardz.dzone.com/refcardz/core-java-concurrency

Be aware that volatile is NOT atomic, which means that double and long which use 64 bits can be read in an inconsistent state where 32 bits are the old value and 32 bits are the new value. 请注意,volatile不是原子的,这意味着可以在不一致的状态下读取使用64位的double和long,其中32位是旧值,32位是新值。 Also, volatile arrays do not make the array entries volatile. 此外,易失性数组不会使数组条目易失。 Using classes from java.util.concurrent is strongly recommended. 强烈建议使用java.util.concurrent中的类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM