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

Mor*_*itz 23 java multithreading final volatile

如果我有一个在线程之间共享的对象,在我看来每个字段应该是final或者volatile,具有以下推理:

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

  • 如果该领域永远不会改变,那就去做吧final.

但是,我找不到任何关于此的内容,所以我想知道这个逻辑是否有缺陷还是太明显?

当然编辑而不是易失性可能使用final AtomicReference或类似.

编辑,例如,请参阅get getter方法是Java中volatile的替代方法吗?

编辑以避免混淆:这个问题是关于缓存失效!如果两个线程对同一个对象进行操作,则可以缓存对象的字段(每个线程),如果它们未声明为volatile.如何保证缓存无效?

最后的编辑感谢@Peter Lawrey,他指出了JLS§17(Java内存模型).据我所知,它表明同步在操作之间建立了先发生关系,因此如果那些更新"发生在之前",则线程会看到来自另一个线程的更新,例如,如果非易失性字段的getter和setter是synchronized.

Pet*_*rey 30

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

  • 慢得多,通常慢10倍左右.
  • 通常不会为您提供所需的线程安全性,但可以通过使它们不太可能出现而更难找到这些错误.
  • 不同的是final,通过说不应该改变它来提高清晰度,使用volatile不需要的时候,可能会引起混淆,因为读者试图弄清楚为什么它变得易变.

如果应该更改字段(指向另一个对象,更新原始值),则该字段应该是volatile,以便所有其他线程对新值进行操作.

虽然这对于读取是好的,但请考虑这个简单的情况.

volatile int x;

x++;
Run Code Online (Sandbox Code Playgroud)

这不是线程安全的.因为它是一样的

int x2 = x;
x2 = x2 + 1; // multiple threads could be executing on the same value at this point.
x = x2;
Run Code Online (Sandbox Code Playgroud)

更糟糕的是,使用volatile会使这种bug更难找到.

随着yshavit点的出现,更新多个字段更难以解决,volatile例如HashMap.put(a, b)更新多个引用.

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

synchronized给你所有的内存保证volatile和更多,这就是为什么它明显变慢.

注意:只是synchronized每个方法并不总是足够的.StringBuffer每个方法都是同步的,但在多线程上下文中是无用的,因为它的使用很可能容易出错.

很容易认为实现线程安全就像洒上仙尘一样,添加一些神奇的线程安全,你的bug就会消失.问题是螺纹安全更像是一个有很多孔的铲斗.插入最大的孔并且这些漏洞看起来会消失,但除非你全部插上它们,否则你没有线程安全,但它可能更难找到.

就同​​步与易失性而言,这表明了这一点

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

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

  • @Moritz同步将毫无意义,没有内存障碍来提供这些保证.当值缓存在synchronized块中时,它将在读取时返回最新值,并确保所有线程都可以看到所写的内容. (3认同)

And*_*ner 8

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

在制作其他领域方面volatile:

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

如果访问synchronized块之外的值,则只能看到缓存的值.

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

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

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