簡體   English   中英

是否可以重新排序實例初始化和分配給共享變量?

[英]Is reordering of instance initialization and assignment to a shared variable possible?

我正在閱讀一篇文章 ,其中涉及雙重檢查鎖定,但我對作為示例提供的代碼中更基本的失敗感到驚訝。 在那里聲明,實例的初始化(即,在構造函數返回之前發生的實例變量的寫入)可能會對實例的引用寫入共享變量(在靜態字段中) 之后重新排序。以下示例)。

使用以下Foo類的定義是否正確,一個線程執行Foo.initFoo(); 和另一個執行System.out.println(Foo.foo.a);線程System.out.println(Foo.foo.a); ,第二個線程可能會打印0 (而不是1或拋出NullPointerException )?

class Foo {
    public int a = 1;

    public static Foo foo;

    public static void initFoo() {
        foo = new Foo();
    }

    public static void thread1() {
        initFoo(); // Executed on one thread.
    }

    public static void thread2() {
        System.out.println(foo.a); // Executed on a different thread
    }
}

根據我對Java內存模型(以及其他語言中的內存模型)的了解,實際上我並不感到驚訝,這是可能的,但直覺投票非常強烈,因為它是不可能的(可能因為涉及對象初始化而對象初始化似乎如此在Java中神聖)。

是否可以在第一個線程中沒有同步的情況下“修復”此代碼(即它永遠不會打印0 )?

調用foo = new Foo(); 涉及幾個可能重新排序的操作,除非你引入適當的同步來防止它:

  1. 為新對象分配內存
  2. 寫字段的默認值( a = 0
  3. 寫字段的初始值( a = 1
  4. 發布對新創建的對象的引用

如果沒有正確的同步,可能會重新排序步驟3和4(請注意,步驟2必須在步驟4之前發生),盡管x86架構上的熱點不太可能發生。

為了防止它你有幾個解決方案,例如:

  • a決賽
  • 同步訪問foo (使用同步的init AND getter)。

在不進入JLS#17的復雜性的情況下,您可以閱讀關於類初始化的JLS#12.4.1 (強調我的):

初始化代碼不受限制的事實允許構造示例,其中在評估其初始化表達式之前,當它仍然具有其初始默認值時可以觀察到類變量的值 ,但是這樣的示例在實踐中是罕見的。 這些示例也可以構造為例如變量初始化 。)這些初始化器中可以使用Java編程語言的全部功能。 程序員必須小心謹慎。 這種能力給代碼生成器帶來了額外的負擔,但是在任何情況下都會產生這種負擔,因為Java編程語言是並發的。

即使在x86下,JIT編譯器的實例初始化重新排序也是可能的。 但是,編寫可以觸發此類重新排序的代碼有點棘手。 關於如何重現這種重新排序,請參閱我的問題:

Hotspot JIT編譯器是否可以重現任何指令重新排序?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM