我有一個線程使用全局變量的多線程應用程序。而且這個變量在主線程中只更新一次。如何強制所有線程使用變量的最新值而不必使用volatile?
什麼是確保所有其他線程在更新時都取最新值的最佳方法?
將此變量定義爲volatile會影響性能,因爲我需要僅在幾天內更新它。
我有一個線程使用全局變量的多線程應用程序。而且這個變量在主線程中只更新一次。如何強制所有線程使用變量的最新值而不必使用volatile?
什麼是確保所有其他線程在更新時都取最新值的最佳方法?
將此變量定義爲volatile會影響性能,因爲我需要僅在幾天內更新它。
聲明你變量爲volatile
,你就完成了。
......但的確如此,在某些情況下可以優化對變量的訪問。您希望100%確定沒有其他變量與您的變量共享緩存行。爲什麼?因爲如果沒有共享發生,則在S(共享)狀態下,所有內核的CPU高速緩存中都有最新版本的變量的副本。讀取這個變量很快就好像它沒有被聲明爲volatile
。但是,如果有其他數據與變量共享緩存行,並且這些其他數據經常被修改(例如每微秒),那麼將阻止希望讀取變量的線程,而最新的變量值將從此緩存中傳輸另一個數據已更新。
爲了證明上述情況,這裏的程序:
import java.util.concurrent.TimeUnit;
public class CacheLineSharing {
static class Holder {
volatile long pad0;
volatile long pad1;
volatile long pad2;
volatile long pad3;
volatile long pad4;
volatile long pad5;
volatile long pad6; // either pad6 or pad7 shares cache line with x
volatile long x = INIT_VALUE;
volatile long pad7;
volatile long pad8;
volatile long pad9;
volatile long pad10;
volatile long pad11;
volatile long pad12;
volatile long pad13;
volatile long pad14; // definitely in different cache line than x
}
static final int INIT_VALUE = 0;
static final int START_VALUE = 1;
static final int FINISH_VALUE = 2;
static class MyThread extends Thread {
Holder holder;
MyThread(Holder holder) {
this.holder = holder;
}
@Override
public void run() {
while (holder.x != START_VALUE);
long cycles = 0;
while (holder.x != FINISH_VALUE) {
cycles++;
}
System.out.println(String.format("cycles=%d", cycles));
}
}
public static void main(String[] args) throws Exception {
Holder holder = new Holder();
for (int i = 0; i < 4; i++) {
new MyThread(holder).start();
}
long ts = System.nanoTime();
holder.x = START_VALUE;
for (int i = 0; i < 1000000000; i++) {
//holder.pad6 = i;
holder.pad14 = i;
}
holder.x = FINISH_VALUE;
long nanoes = System.nanoTime() - ts;
System.out.println(String.format("millis=%d", TimeUnit.NANOSECONDS.toMillis(nanoes)));
}
}
我們的目標變量是x
我們衡量如何快速讀取它的值。在我們不斷更新pad6
變量第一種情況下(它有6 7 x
機會共享高速緩存行的):每秒
millis=4317
cycles=584637722
cycles=655006968
cycles=1214910177
cycles=1133123641
〜280K迭代最快螺紋
現在同樣的程序更新pad14
變量不與共享x
高速緩存行(現代英特爾CPU具有64個字節高速緩存行= 8的java多頭):每秒
cycles=1857323945
cycles=2034309531
cycles=1820202891
millis=1363
cycles=2083430201
〜1335K迭代即使對於slowe st線程。
快4.75倍!因此,如果你對優化感到非常憤怒,那麼在你之前和之後的7個多長時間內,你的變量需要填充7個長整型。
避免虛假分享並不能立即保證跨線程的可視性,即使對於單個作者也是如此。填充變量並不以任何方式確保對其進行的更改將立即傳播到緩存子系統,而不是保存在存儲緩衝區中。另外請記住,使用硬編碼數字的「長」的填充不是可移植的(POWER8使用128字節的l1d高速緩存行) - 如果您使用Java 8,只需使用'@ Contended'。如果你不使用像JMH這樣的工具,很難說你沒有測量任何一些無關和微妙的副作用。 –
@DimitarDimitrov,如果我們正在談論現代英特爾的CPU,那麼立即可以看到更改。這條規則的例外情況是:1)使用SIMD指令(可能將結果緩存在存儲緩衝區中); 2)使用明確標記爲內存類型的內存地址範圍,並且保證弱於回寫可緩存。 (當然,這種解決方案不適用於其他架構,問題通常是可移植性與性能相矛盾。) – gudok
將賦值置於'synchronized'塊中。 –
@AndyTurner更高效的閱讀? –
讀取不在同步塊中。只有分配。你需要適當地選擇顯示器。 –