Java 非易失性共享变量的损坏

Java 非易失性共享变量的损坏,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我知道volatile的可见性保证。我不是在问那件事 我刚刚审阅了以下代码: 类MyController{ private int someId=-1;//未初始化状态 公共无效HandlerRequest(){ //从多个线程调用,传入HTTP请求 var id=getSomeId(); //做一些涉及身份证的事情 } 私有int getSomeId(){ if(this.someId==-1){ this.someId=fetchIDFromSomewhere(); } 返回这个.someI

我知道
volatile
的可见性保证。我不是在问那件事

我刚刚审阅了以下代码:

类MyController{
private int someId=-1;//未初始化状态
公共无效HandlerRequest(){
//从多个线程调用,传入HTTP请求
var id=getSomeId();
//做一些涉及身份证的事情
}
私有int getSomeId(){
if(this.someId==-1){
this.someId=fetchIDFromSomewhere();
}
返回这个.someId;
}
private int fetchidFromSomWheresel()
{
//昂贵但幂等的,总是返回一个正值
}
}
一位同事评论说,我必须使
MyController#someId
变量不稳定,否则我将冒读取损坏值的风险

我假设
getSomeId()
不可能返回负值(因为
fetchidfromsomether
从不返回负值)。如果我错了,请纠正我

问题:在上面的代码中,
getSomeId()
是否可能返回从未写入
someId
实例变量的值? 答案是否随数据类型和体系结构而变化?int/long,32位/64位? 我想

  • 变量将始终在单词边界上对齐
  • 因此,CPU总是在一个原子操作中将整个变量写入内存
  • 因此,32位机器上的
    long
    可能会在这里引起故障
我确实意识到,
fetchidfromsomethine
可能会被调用多次,而不是一次

我无法给出一个在现代64位处理器上int或long被损坏的示例。

否 不带
int
。发件人:

17.7. 双金属和长金属的非原子处理 对于Java编程语言内存模型而言,对非易失性长或双值的单次写入被视为两次单独的写入:每32位半写一次。这可能导致线程在一次写入中看到64位值的前32位,在另一次写入中看到第二个32位

[……]

对我来说,这清楚地表明,任何JVM实现都必须能够以原子方式读写32位。将此与17.6结合使用:

17.6. 撕字 Java虚拟机实现的一个考虑因素是,每个字段和数组元素都被认为是不同的一个字段或元素的更新不得与任何其他字段或元素的读取或更新交互。[…]

很明显,对int字段的写入是原子的


但它也得出结论,共享的非易失性
long
s和
double
s在跨线程使用且没有
synchronized
时,可能会以非常不理想的方式运行。我必须同意我的同事的看法,即最好只标记每个共享变量
volatile
,除非存在无法容忍的明显性能影响

也从17.7开始:

[……]

对引用的写入和读取始终是原子的,无论它们是作为32位值还是64位值实现的

[……]

这意味着JVM必须对任何非原语强制使用volatile语义,这使得引用字段上的
volatile
在最坏的情况下是多余的



感谢@markspace为我提供了术语
word-writing
,用于谷歌搜索:)

您将不会得到未写入该变量的值。如果该变量不是易失的,您可能会得到写入该变量的任何一个值。Java保证您永远不会看到“已损坏”的值(从未写入且只是凭空提取的值)。对于32位系统上运行的64位原语,规范中允许看到“字撕裂”,即写入的64位值的一半,而另一半尚未更新。这看起来肯定很像腐败。在64位系统上不允许这种字撕裂。@markspace我在哪里可以找到保证
fetchidfromsomether
不会直接修改
this.someId
,并在执行时将中间值存储到其中?是什么使它不是合法的Java优化?您必须从库或实现
fetchidFromSomewhere()
的人员那里获得保证。这东西不是魔法。该方法要么访问
someId
(我注意到它是私有的,所以它不应该被访问),要么不被访问。这里不应该有任何猜测。@markspace实现它的人如何知道实现是否优化了它以避免使用临时命令?他们应该查看生成的字节码吗?那会有什么帮助呢?运行时也可以优化代码。是什么阻止编译器优化this.someId=fetchIDFromSomeWhere()进入第一个设置
this.someId
引用一个值为零(或其他值)的整数,然后让
fetchidFromSomewhere
更改该值,允许另一个线程在一个窗口中从
this.someId
读取既不是-1也不是正确值的值?见鬼,是什么阻止
fetchidfromsomether
用中间值在内部修改
this.someId
?什么法律要求编译器使用临时或中间位置?@DavidSchwartz
fetch
中的代码不相关。te JLS禁止编译器和运行时在源代码中没有显式赋值语句的情况下接触字段。我问这个字段是否可以有一个没有赋值的值(就像单词撕裂一样),但我从来没有问过赋值在哪里。如果
fetch
触及该字段,则外部方法以