如何强制所有线程使用变量的最新值而不必使用volatile?

时间:2016-04-25 10:27:35

标签: java multithreading synchronization

我有一个多线程应用程序,其中线程使用全局变量。此变量仅在主线程中更新一次。

确保所有其他线程在更新时采用最新值的最佳方法是什么?

将此变量定义为volatile会影响性能,因为我需要在几天内更新一次。

1 个答案:

答案 0 :(得分:0)

将变量声明为volatile,您就完成了。

...但确实可以在某些情况下优化对变量的访问。您希望100%确定没有其他变量与您的变量共享缓存行。为什么?因为如果不发生共享,则在 S (共享)状态的所有核心的CPU缓存中都可以复制最新版本的变量。读取此变量很快就好像它没有声明为volatile。但是如果有其他数据共享缓存行与您的变量和其他数据被频繁修改(例如每微秒),比希望读取您的变量的线程被阻止,而您的变量的最新值从CPU的缓存转移另一项数据已更新。

为了演示上述场景,这里是程序:

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变量(它有7次机会与x共享缓存行):

millis=4317
cycles=584637722
cycles=655006968
cycles=1214910177
cycles=1133123641

最快线程每秒~280K迭代次数

现在相同的程序更新pad14变量,该变量不与x共享缓存行(现代Intel CPU的缓存行为64字节= 8个java长):

cycles=1857323945
cycles=2034309531
cycles=1820202891
millis=1363
cycles=2083430201
即使是最慢的线程,

〜每秒1335K次迭代。

快4.75倍!

所以,如果你对优化感到非常生气,那么在变量7之前填充你的变量并且在7之后填充变量。