簡體   English   中英

java:帶有getter和setter的`volatile`私有字段

[英]java: `volatile` private fields with getters and setters

如果實例在多個線程中使用,我們是否應該將私有字段聲明為volatile

Effective Java中 ,有一個例子,代碼在沒有volatile的情況下不起作用:

import java.util.concurrent.TimeUnit;

// Broken! - How long would you expect this program to run?
public class StopThread {
    private static boolean stopRequested; // works, if volatile is here

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

解釋說

while(!stopRequested)
    i++;

優化到這樣的事情:

if(!stopRequested)
    while(true)
        i++;

所以后台線程看不到stopRequested進一步修改,所以它永遠循環。 (順便說一句,該代碼在JRE7上沒有volatile情況下終止。)

現在考慮這個類:

public class Bean {
    private boolean field = true;

    public boolean getField() {
        return field;
    }

    public void setField(boolean value) {
        field = value;
    }
}

和一個如下的線程:

public class Worker implements Runnable {
    private Bean b;

    public Worker(Bean b) {
        this.b = b;
    }

    @Override
    public void run() {
        while(b.getField()) {
            System.err.println("Waiting...");
            try { Thread.sleep(1000); }
            catch(InterruptedException ie) { return; }
        }
    }
}

上面的代碼按預期工作,不使用volatile:

public class VolatileTest {
    public static void main(String [] args) throws Exception {
        Bean b = new Bean();

        Thread t = new Thread(new Worker(b));
        t.start();
        Thread.sleep(3000);

        b.setField(false); // stops the child thread
        System.err.println("Waiting the child thread to quit");
        t.join();
        // if the code gets, here the child thread is stopped
        // and it really gets, with JRE7, 6 with -server, -client
    }
}

我認為由於公共setter,編譯器/ JVM永遠不應該優化調用getField()的代碼,但本文說有一些“Volatile Bean”模式(模式#4),應該應用於創建可變線程 - 安全課程。 更新:也許該文章僅適用於IBM JVM?

問題是:JLS的哪一部分明確地或隱含地說具有公共getter / setter的私有原始字段必須聲明為volatile (或者它們不必)?

很抱歉,我試圖詳細解釋這個問題。 如果有什么不清楚,請告訴我。 謝謝。

問題是:JLS的哪一部分明確地或隱含地說具有公共getter / setter的私有原始字段必須聲明為volatile(或者它們不必)?

JLS內存模型不關心getter / setter。 從內存模型的角度來看,它們是無操作的 - 你也可以訪問公共領域。 將布爾值包裝在方法調用后面不會影響其內存可見性。 你的后一個例子純粹是運氣。

如果實例在多個線程中使用,我們是否應該將私有字段聲明為volatile?

如果要在多線程環境中使用類(bean),則必須以某種方式將其考慮在內。 使私有字段volatile是一種方法:它確保每個線程都能保證看到該字段的最新值,而不是任何緩存/優化掉過時值的東西。 但它並沒有解決原子性問題。

您鏈接的文章適用於任何符合JVM規范(JLS傾向於此)的JVM。 您將獲得各種結果,具體取決於JVM供應商,版本,標志,計算機和操作系統,運行程序的次數(HotSpot優化經常在第10000次運行后啟動)等,因此您必須了解規范並仔細遵守遵守規則以創建可靠的計划。 在這種情況下進行試驗是一種很難找到工作方式的方法,因為JVM可以以任何方式運行,只要它符合規范,並且大多數JVM確實包含所有類型的動態優化。

不,該代碼同樣不正確。 JLS中沒有任何內容表示必須將字段聲明為volatile。 但是,如果您希望代碼在多線程環境中正常工作,則必須遵守可見性規則。 volatile和synchronized是兩個正確使數據在線程間可見的主要工具。

至於你的例子,編寫多線程代碼的難點在於許多形式的錯誤代碼在測試中都能正常工作。 僅僅因為多線程測試在測試中“成功”並不意味着它是正確的代碼。

有關特定的JLS參考,請參閱“ 發生前”部分(以及頁面的其余部分)。

請注意,作為一般的經驗法則,如果您認為自己想出了一種巧妙的新方法來繞過“標准”線程安全習語,那么您很可能是錯的。

在我回答你的問題之前,我想解決

順便說一下,該代碼在JRE7上沒有volatile就終止了

如果您使用不同的運行時參數部署相同的應用程序,則可能會更改。 吊裝不一定是JVM的默認實現,因此它可以在一個而不是另一個中工作。

要回答你的問題,沒有什么能阻止Java編譯器執行你的后一個例子

@Override
public void run() {
    if(b.getField()){
        while(true) {
            System.err.println("Waiting...");
            try { Thread.sleep(1000); }
            catch(InterruptedException ie) { return; }
        }
    }
}

它仍然是順序一致的 ,因此保持了Java的保證 - 你可以具體閱讀17.4.3

在由每個線程t執行的所有線程間動作中,t的程序順序是反映根據t的線程內語義將執行這些動作的順序的總順序。

如果所有動作都以與程序順序一致的總順序(執行順序)發生,則一組動作是順序一致的,此外,變量v的每個讀取r將寫入w寫入的值看作v,使得:

換句話說 - 只要線程將以相同的順序看到字段的讀取和寫入,而不管編譯器/內存的重新排序,它被認為是順序一致的。

暫無
暫無

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

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