Java 解释JIT重新排序是如何工作的
我已经读了很多关于Java中的同步以及可能出现的所有问题的书。然而,我仍然有点困惑的是JIT如何对写入进行重新排序 例如,一个简单的双重检查锁对我来说很有意义:Java 解释JIT重新排序是如何工作的,java,multithreading,synchronization,jit,Java,Multithreading,Synchronization,Jit,我已经读了很多关于Java中的同步以及可能出现的所有问题的书。然而,我仍然有点困惑的是JIT如何对写入进行重新排序 例如,一个简单的双重检查锁对我来说很有意义: class Foo { private volatile Helper helper = null; // 1 public Helper getHelper() { // 2 if (helper == null) { // 3 synchronized(this) { //
class Foo {
private volatile Helper helper = null; // 1
public Helper getHelper() { // 2
if (helper == null) { // 3
synchronized(this) { // 4
if (helper == null) // 5
helper = new Helper(); // 6
}
}
return helper;
}
}
我们在第1行使用volatile来强制执行“发生在发生之前”关系。如果没有它,JIT完全有可能重新排序我们的代码。例如:
helper
,但是,构造函数尚未运行,因为JIT可能会对代码重新排序我理解这一点,但我不完全理解JIT对重新排序的限制 例如,假设我有一个方法,可以创建
MyObject
并将其放入HashMap
(我知道HashMap
不是线程安全的,不应该在多线程环境中使用,但请耐心等待)。线程1调用createNewObject:
public class MyObject {
private Double value = null;
public MyObject(Double value) {
this.value = value;
}
}
Map<String, MyObject> map = new HashMap<String, MyObject>();
public void createNewObject(String key, Double val){
map.put(key, new MyObject( val ));
}
线程2是否可以从getObject(字符串键)
接收未完全构造的对象?比如:
新MyObject(val)
getObject(字符串键)
map.put(key,newmyobject(val))
在对象完全构建之前不会将其放入映射中吗
我想答案是,它不会把一个对象放到地图上,直到它被完全构造(因为这听起来很糟糕)。那么JIT如何重新排序呢
简而言之,它只能在创建一个新的对象
并将其分配给一个引用变量(如双重选中锁)时重新排序吗?JIT的完整运行对于SO答案来说可能很重要,但我真正好奇的是它如何重新排序写入(如双重检查锁上的第6行),以及如何阻止它将对象放入未完全构造的映射中。警告:文本墙
你问题的答案在水平线之前。我将在回答的第二部分继续深入解释基本问题(这与JIT无关,所以如果您只对JIT感兴趣,就到此为止)。你问题第二部分的答案在底部,因为它取决于我进一步描述的内容
TL;DR JIT将做它想做的任何事情,JMM将做它想做的任何事情,在您通过编写线程不安全代码允许他们这样做的条件下是有效的
注意:“初始化”指的是构造函数中发生的事情,它排除了其他任何事情,例如在构造后调用静态init方法等
“如果重新排序产生的结果与合法执行一致,则不违法。”()
如果一组操作的结果符合JMM规定的有效执行链,则无论作者是否希望代码产生该结果,都允许该结果
“内存模型描述程序的可能行为。只要程序的所有结果执行产生内存模型可以预测的结果,实现就可以自由生成它喜欢的任何代码
这为实现者执行大量代码转换提供了很大的自由,包括重新排序操作和删除不必要的同步“()
JIT将重新排序它认为合适的任何东西,除非我们不允许它使用JMM(在多线程环境中)
JIT可以或将要做什么的细节是不确定的。查看数以百万计的运行样本不会产生有意义的模式,因为重新排序是主观的,它们取决于非常具体的细节,如CPU架构、计时、启发式、图形大小、JVM供应商、字节码大小等。。。我们只知道JIT将假设代码在不需要符合JMM的情况下在单线程环境中运行。最后,JIT对您的多线程代码影响不大。如果您想深入挖掘,请参阅本文,并对诸如、和编译器文章之类的主题进行一些研究。但是,请再次记住,JIT与多线程代码转换关系不大
实际上,“尚未完全创建的对象”不是JIT的副作用,而是内存模型(JMM)。总之,JMM是一个规范,它提供了对某组操作的结果的保证,其中操作是涉及共享状态的操作。JMM更容易被更高层次的概念所理解,例如,这三个概念是线程安全程序的组件
为了证明这一点,您的第一个代码示例(DCL模式)不太可能被JIT修改,从而产生“尚未完全创建的对象”。事实上,我认为这是不可能的,因为它不会遵循单线程程序的顺序或执行。
那么这里的问题到底是什么呢
问题是,如果操作不是按同步顺序排序的,则在顺序之前发生,等等。。。(再次描述)则不能保证线程看到执行此类操作的副作用线程可能不会刷新其缓存以更新字段,线程可能会观察到无序写入。特定于此示例,允许线程查看处于不一致状态的对象,因为该对象未正确发布。我敢肯定,如果您曾经使用过多线程技术,那么您一定听说过安全发布
您可能会问,如果JIT不能修改单线程执行,为什么多线程
public MyObject getObject(String key){
return map.get(key);
}
T1 -> get() MyObject=30 ------> +1 --------------> put(MyObject=31)
T2 -------> get() MyObject=30 -------> +1 -------> put(MyObject=31)