为什么Final字段在Java中不能挥发?

Gee*_*eek 15 java volatile

我想了解为什么声明为final的Reference不能声明为Volatile.在SO上有一个类似的问题[ 为什么Object成员变量在Java中不是最终的和易变的?

[1]:为什么Object成员变量在Java中不能是final和volatile?但我不确定在答案中是否理解了FINAL.

现在,最终变量的状态在初始化后肯定可以更改.只能将引用初始化为另一个对象.

例如,考虑以下成员变量

final StringBuilder sb = new StringBuilder("CAT");
Run Code Online (Sandbox Code Playgroud)

现在另一个线程改变sb为:

sb.append("S");
Run Code Online (Sandbox Code Playgroud)

如果此变量是非易失性的,那么根据Java内存模型,此更改是否可用于不同的线程?

编辑:我把StringBuffer改为StringBuilder让一些人明白我的观点.

Joh*_*int 16

volatile意味着该领域将发生变化.如果它是最终的,你将永远不会改变它,这是没有意义的.

如果此变量是非易失性的,那么根据Java内存模型,此更改是否可用于不同的线程?

在发生易失性写入后,声明字段volatile对其内容或修改没有影响.如果字段是易失性或非易失性,则内存效应append由StringBuffer的实现决定.

因此,在StringBuffer的情况下,它确实可以确保内存可见性,但不是出于您的想法.StringBuffer是同步的(线程安全的),因此您将拥有内容的始终最新值.

另一方面,StringBuilder未同步,并且无法保证内存可见性.因此,如果您尝试将两者交换为多线程测试,您会看到不同的结果.

  • @Geek,volatile也只意味着引用是线程安全的,它并不意味着引用的内容是线程安全的. (2认同)

Mar*_*aux 9

因为volatile影响Threads访问和编辑变量的行为.在您的代码示例中,变量是sb对象的reference().因此,这意味着volatile没有对效果的对象.final锁定引用.因此,如果您附加文本,则不会更改引用.因此,它是没有意义的使用volatile,并final在同一时间.

如果此变量是非易失性的,那么根据Java内存模型,此更改是否可用于不同的线程?

因为您使用StringBuilder使用非线程安全实现,所以无法保证所有线程都具有StringBuilder的最新状态.


nos*_*nos 6

如果此变量是非易失性的,则根据Java内存模型,此更改是否可用于不同的线程

是的,但那是因为StringBuffer是线程安全的 - 这意味着它在内部提供锁定,这将导致内存障碍,以便其他线程看到更新.

volatile是一个引用不会影响对象的操作,它会影响引用.

所以你可以做到

 volatile StringBuilder b = new StringBuilder();

 b = someOtherStringBuilder;
Run Code Online (Sandbox Code Playgroud)

既然b是volatile,其他线程会看到这个引用的更新.

无论如何

 b.append("foo");
Run Code Online (Sandbox Code Playgroud)

无法保证其他线程会看到对现有b对象的更改.与StringBuffer不同,StringBuilder不是线程安全的,所以如果不提供自己的锁定,你不应该这样做.

如果您希望保证b.append("foo");在没有任何锁定的情况下对其他线程可见,则StringBuilder中的每个成员字段也需要是volatile.(虽然这不会使线程安全)

  • @Geek是的我确信这一点.如上所述,如果您需要与其他线程共享此StringBuilder,则无论如何都需要在使用该StringBuilder对象时提供自己的锁定/同步.这些锁将提供所需的内存屏障,以便可以看到更改. (2认同)

use*_*674 5

请记住,volatilefinal影响变量/成员,但影响其中命名/引用的对象的可变性(或操作的线程安全性)!(其他答案解释了为什么修饰符不能一起使用。)

在给定的情况下,sb.append(..) 修改命名/引用对象(尽管它不会修改变量/成员)并且不安全 使用任一修饰符进行跨线程。


正如其他人指出的那样,StringBuilder添加了一些内部同步,但这仅意味着它不会在其内部数据结构级别上“变得损坏”。

但是,切换到StringBuilder仍然不是 “固有”线程安全的。线程安全性与各种动作的原子范围有很大关系-并且在没有看到所有活动的情况下进行判断以及如何允许其以有效执行方式进行交互将产生误导。

例如,考虑以下从多个线程执行的代码(并假定sb现在是StringBuilder对象)。订单是什么?

sb.append("a").append("b");
Run Code Online (Sandbox Code Playgroud)

如果代码更改如下,会发生什么?[更多]“线程安全”是哪个?

synchronized (this) {
  sb.append("a").append("b");
}
Run Code Online (Sandbox Code Playgroud)