简体   繁体   English

Java:将所有字段设为final还是volatile?

[英]Java: Make all fields either final or volatile?

If I have an object which is shared between threads, it seems to me that every field should be either final or volatile , with the following reasoning: 如果我有一个在线程之间共享的对象,在我看来,每个字段应该是finalvolatile ,具有以下推理:

  • if the field should be changed (point to another object, update the primitive value), then the field should be volatile so that all other threads operate on the new value. 如果应该更改字段(指向另一个对象,更新原始值),则该字段应该是volatile以便所有其他线程对新值进行操作。 Merely a synchronization on the methods which access said field is insufficient because they might return a cached value. 仅仅访问所述字段的方法的同步是不够的,因为它们可能返回缓存的值。

  • if the field should never change, make it final . 如果该领域永远不会改变,那就让它成为final

However, I could not find anything about this, so I wonder whether this logic is flawed or just too obvious? 但是,我找不到任何关于此的内容,所以我想知道这个逻辑是否有缺陷还是太明显?

EDIT of course instead of volatile one might use a final AtomicReference or similar. 当然编辑而不是volatile可能会使用final AtomicReference或类似的。

EDIT for example, see Is getter method an alternative to volatile in Java? 编辑 ,例如,请参阅get getter方法是Java中volatile的替代方法吗?

EDIT to avoid confusions: This question is about cache invalidation! 编辑以避免混淆: 这个问题是关于缓存失效! If two threads operate on the same object, the fields of the objects can be cached (per thread), if they are not declared volatile. 如果两个线程对同一个对象进行操作,则可以缓存对象的字段(每个线程),如果它们未声明为volatile。 How can I guarantee that the cache is invalidated properly? 如何保证缓存无效?

FINAL EDIT Thanks to @Peter Lawrey who pointed me to JLS §17 (Java memory model). 最后的编辑感谢@Peter Lawrey,他指出了JLS§17(Java内存模型)。 As far as I see, it states that synchronization establishes a happens-before relation between operations, so that a thread sees the updates from another thread if those updates "happened-before", eg if getter and setter for a non-volatile field are synchronized . 据我所知,它表明同步在操作之间建立了先发生关系,因此如果那些更新“发生在之前”,则线程会看到来自另一个线程的更新,例如,如果非易失性字段的getter和setter是synchronized

While I feel private final should probably have been the default for fields and variables with a keyword like var making it mutable, using volatile when you don't need it is 虽然我认为private final应该是字段和变量的默认值,其中包含var这样的关键字使其变得可变,但是当你不需要它时使用volatile是

  • much slower, often around 10x slower. 慢得多,通常慢10倍左右。
  • usually doesn't give you the thread safety you need, but can make finding such bugs harder by making them less likely to appear. 通常不会为您提供所需的线程安全性,但可以通过使它们不太可能出现而更难发现这些错误。
  • unlike final which improves clarity by saying this shouldn't be altered, using volatile when it is not needed, is likely to be confusing as the reader tries to work out why it was made volatile. 不像final那样通过说不应该改变它来提高清晰度,在不需要时使用volatile ,可能会让读者试图弄清楚为什么它变得不稳定会让人感到困惑。

if the field should be changed (point to another object, update the primitive value), then the field should be volatile so that all other threads operate on the new value. 如果应该更改字段(指向另一个对象,更新原始值),则该字段应该是volatile,以便所有其他线程对新值进行操作。

While this is fine for reads, consider this trivial case. 虽然这对于读取是好的,但请考虑这个简单的情况。

volatile int x;

x++;

This isn't thread-safe. 这不是线程安全的。 As it's the same as 因为它是一样的

int x2 = x;
x2 = x2 + 1; // multiple threads could be executing on the same value at this point.
x = x2;

What is worse is that using volatile would make this kind of bug harder to find. 更糟糕的是,使用volatile会使这种bug更难找到。

As yshavit point's out, updating multiple fields is harder to work around with volatile eg HashMap.put(a, b) updates multiple references. 正如yshavit指出的那样,更新多个字段更难以解决volatile例如HashMap.put(a, b)更新多个引用。

Merely a synchronization on the methods which access said field is insufficient because they might return a cached value. 仅仅访问所述字段的方法的同步是不够的,因为它们可能返回缓存的值。

synchronized gives you all the memory guarantees of volatile and more, which is why it's significantly slower. synchronized给你所有内存保证的volatile和更多,这就是它明显变慢的原因。

NOTE: Just synchronized -ing every method isn't always enough either. 注意:只是synchronized每个方法并不总是足够的。 StringBuffer has every method synchronized but is worst than useless in a multi-threaded context as it's use is likely to be error-prone. StringBuffer使每个方法都同步,但在多线程上下文中是无用的,因为它的使用很可能容易出错。

It's too easy to assume that achieving thread safety is like sprinkling fairy dust, add some magic thread safety and your bugs go away. 很容易认为实现线程安全就像洒上仙尘一样,添加一些神奇的线程安全,你的bug就会消失。 The problem is that thread safety is more like a bucket with many holes. 问题是螺纹安全更像是一个有很多孔的铲斗。 Plug the biggest holes and the bugs can appear to go away, but unless you plug them all, you don't have thread safety, but it can be harder to find. 插入最大的孔并且这些漏洞看起来会消失,但除非你全部插上它们,否则你没有线程安全,但它可能更难找到。

In terms of synchronized vs volatile, this states 就同​​步与易失性而言,这表明了这一点

Other mechanisms, such as reads and writes of volatile variables and the use of classes in the java.util.concurrent package, provide alternative ways of synchronization. 其他机制,例如volatile变量的读写和java.util.concurrent包中的类的使用,提供了替代的同步方法。

https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

Making fields you don't need to change final is a good idea, irrespective of threading concerns. 无论线程问题如何,创建不需要更改final字段都是一个好主意。 It makes instances of the class easier to reason about, because you can know what state it is in more easily. 它使类的实例更容易推理,因为你可以更容易地知道它的状态。

In terms of making the other fields volatile : 在使其他字段volatile

Merely a synchronization on the methods which access said field is insufficient because they might return a cached value. 仅仅访问所述字段的方法的同步是不够的,因为它们可能返回缓存的值。

You would only see a cached value if you accessing the value outside a synchronized block. 如果访问synchronized块之外的值,则只能看到缓存的值。

All accesses would need to be correctly synchronized. 所有访问都需要正确同步。 The end of one synchronized block is guaranteed to happen before the start of another synchronized block (when synchronizing on the same monitor). 保证在另一个同步块的启动之前(在同一监视器上同步时)发生一个同步块的结束。

There are at least a couple of cases where you would still need to use synchronization: 至少有几种情况你仍然需要使用同步:

  • You would want to use synchronization if you had to read and then update one or more fields atomically. 如果必须以原子方式读取并更新一个或多个字段,则需要使用同步。
    • You may be able to avoid synchronization for certain single field updates, eg if you can use an Atomic* class instead of a "plain old field"; 您可以避免某些单个字段更新的同步,例如,如果您可以使用Atomic*类而不是“普通旧字段”; but even for a single field update, you could still require exclusive access (eg adding one element to a list whilst removing another). 但即使对于单个字段更新,您仍然可能需要独占访问权限(例如,将一个元素添加到列表中,同时删除另一个元素)。
  • Also, volatile/final may be insufficient for non-thread safe values, like an ArrayList or an array. 此外,volatile / final可能不足以用于非线程安全值,如ArrayList或数组。

If an object is shared between threads, you have two clear options: 如果线程之间共享一个对象,则有两个明确的选项:

1. Make that object read only 1.将该对象设为只读

So, updates (or cache) have no impact. 因此,更新(或缓存)没有任何影响。

2. Synchronize on object itself 2.同步对象本身

Cache invalidation is hard. 缓存失效很难。 Very hard. 很难。 So if you need to guarantee no stale values, you should protect that value and protect the lock around said value. 因此,如果您需要保证没有陈旧的值,您应该保护该值并保护围绕所述值的锁定

Make the lock and values as private on shared object, so the operations here are a implementation detail. 在共享对象上将锁和值设置为私有,因此这里的操作是实现细节。

To avoid dead locks, this operations should be as "atomic", as in to avoid interact with other any lock. 为避免死锁,此操作应为“原子”,以避免与其他任何锁相互作用。

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

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