Java:使所有字段都是final还是volatile?

Java:使所有字段都是final还是volatile?,java,multithreading,volatile,final,Java,Multithreading,Volatile,Final,如果我有一个在线程之间共享的对象,那么在我看来,每个字段都应该是final或volatile,理由如下: 如果该字段应该更改(指向另一个对象,更新原语值),那么该字段应该是易变的,以便所有其他线程都对新值进行操作。仅仅对访问所述字段的方法进行同步是不够的,因为它们可能返回缓存的值 如果该字段永远不应更改,则将其设置为final 然而,我找不到关于这一点的任何东西,所以我想知道这一逻辑是否有缺陷或太明显了 编辑当然,可以使用最终原子引用或类似引用来代替volatile 编辑例如,请参见 编辑以

如果我有一个在线程之间共享的对象,那么在我看来,每个字段都应该是
final
volatile
,理由如下:

  • 如果该字段应该更改(指向另一个对象,更新原语值),那么该字段应该是易变的,以便所有其他线程都对新值进行操作。仅仅对访问所述字段的方法进行同步是不够的,因为它们可能返回缓存的值

  • 如果该字段永远不应更改,则将其设置为
    final

然而,我找不到关于这一点的任何东西,所以我想知道这一逻辑是否有缺陷或太明显了

编辑当然,可以使用
最终原子引用
或类似引用来代替volatile

编辑例如,请参见

编辑以避免混淆:这个问题是关于缓存失效的如果两个线程在同一个对象上运行,那么如果对象的字段未声明为volatile,则可以缓存(每个线程)。如何保证缓存正确失效


最终编辑感谢@Peter Lawrey,他为我指出了JLS§17(Java内存模型)。据我所见,它指出同步在操作之间建立了一个“发生在之前”的关系,这样一个线程就可以看到来自另一个线程的更新,如果这些更新“发生在之前”,例如,如果非易失性字段的getter和setter是
同步的

则无论线程问题如何,创建不需要更改的
final
字段都是一个好主意。它使类的实例更容易推理,因为您可以更容易地知道它处于什么状态

在使其他字段
不稳定方面

仅仅对访问所述字段的方法进行同步是不够的,因为它们可能返回缓存的值

只有在访问同步块外部的值时,才会看到缓存的值

所有访问都需要正确同步。一个同步块的结束保证发生在另一个同步块的开始之前(在同一监视器上同步时)

至少有两种情况仍然需要使用同步:

  • 如果必须以原子方式读取并更新一个或多个字段,则需要使用同步。
    • 您可以避免某些单字段更新的同步,例如,如果您可以使用
      原子*
      类而不是“普通旧字段”;但是,即使是单个字段更新,您也可能需要独占访问(例如,将一个元素添加到列表中,同时删除另一个元素)
  • 此外,volatile/final可能不足以用于非线程安全的值,例如
    ArrayList
    或数组

虽然我觉得
private final
应该是字段和变量的默认值,带有
var
这样的关键字,使其可变,在不需要时使用volatile

  • 慢得多,通常慢10倍左右
  • 通常不会为您提供所需的线程安全性,但会降低这些bug出现的可能性,从而使查找这些bug变得更加困难
  • 不像
    final
    那样,final通过说这不应该被更改来提高清晰度,在不需要时使用
    volatile
    ,在读者试图弄清楚它为什么被设置为volatile时可能会令人困惑
如果该字段应该更改(指向另一个对象,更新原语值),那么该字段应该是可变的,以便所有其他线程对新值进行操作

<> P>这对于阅读来说很好,请考虑这个小事。

volatile int x;

x++;
这不是线程安全的。因为它和

int x2 = x;
x2 = x2 + 1; // multiple threads could be executing on the same value at this point.
x = x2;
更糟糕的是,使用
volatile
会使这种bug更难发现

正如yshavit所指出的,使用
volatile
更新多个字段比较困难,例如
HashMap.put(a,b)
更新多个引用

仅仅对访问所述字段的方法进行同步是不够的,因为它们可能返回缓存的值

synchronized为您提供了
volatile
和更多的内存保证,这就是它速度显著较慢的原因

注意:仅仅同步每个方法并不总是足够的
StringBuffer
使每个方法都同步,但在多线程上下文中最糟糕的莫过于无用,因为它的使用可能容易出错

人们很容易认为实现线程安全就像撒上仙尘,加上一些神奇的线程安全,你的bug就会消失。问题是螺纹安全更像是一个有许多孔的桶。塞住最大的漏洞,bug可能会消失,但除非你把它们全部塞住,否则你就没有线程安全性,但这可能更难找到

就同步与易失性而言,这表明

其他机制,如易变变量的读写和java.util.concurrent包中类的使用,提供了其他同步方法


如果在线程之间共享对象,则有两个清除选项:

1。将该对象设为只读

因此,更新(或缓存)没有影响

2。在对象本身上同步

缓存失效很难实现。因此,如果您需要保证没有过时的值,那么应该保护该值并保护该值周围的锁

在共享对象上将锁和值设为私有,因此这里的操作是一个实现细节


为避免死锁,此操作应为“原子”操作,以避免与其他任何锁交互。

仅对访问所述字段的方法进行同步