如果线程修改的变量标记为volatile,为什么false共享问题

Dav*_*les 8 java multithreading caching

我一直在看Martin Thompson的文章.这是对虚假分享的解释.

http://mechanical-sympathy.blogspot.co.uk/2011/07/false-sharing.html

    public final class FalseSharing
    implements Runnable
    {
        public final static int NUM_THREADS = 4; // change
        public final static long ITERATIONS = 500L * 1000L * 1000L;
        private final int arrayIndex;

        private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];


        static
        {    
            for (int i = 0; i < longs.length; i++)
            {
                longs[i] = new VolatileLong();
            }
        }

        public FalseSharing(final int arrayIndex)
        {
            this.arrayIndex = arrayIndex;
        }

        public static void main(final String[] args) throws Exception
        {
            final long start = System.nanoTime();
            runTest();
            System.out.println("duration = " + (System.nanoTime() -start));
        }

        private static void runTest() throws InterruptedException
        {
            Thread[] threads = new Thread[NUM_THREADS];

            for (int i = 0; i < threads.length; i++)
            {
                threads[i] = new Thread(new FalseSharing(i));
            }

            for (Thread t : threads)
            {
                t.start();
            }

            for (Thread t : threads)
            {
                t.join();
            }
        }

        public void run()
        {
            long i = ITERATIONS + 1;
            while (0 != --i)
            {
                longs[arrayIndex].value = i;
            }
        }

        public final static class VolatileLong
        {
            public volatile long value = 0L;
            public long p1, p2, p3, p4, p5, p6; // comment out
        }
    }
Run Code Online (Sandbox Code Playgroud)

该示例演示了多个线程使彼此的高速缓存行无效所经历的减速,即使每个线程仅仅更新一个变量.

上面的Blockq图1.说明了错误共享的问题.在核心1上运行的线程想要更​​新变量X,而核心2上的线程想要更​​新变量Y.不幸的是,这两个热变量位于同一缓存行中.每个线程都将竞争缓存行的所有权,以便他们可以更新它.如果核心1获得所有权,那么缓存子系统将需要使核心2的相应缓存行无效.当Core 2获得所有权并执行其更新时,将告知核心1使其缓存行的副本无效.这将通过L3缓存来回乒乓,极大地影响性能.如果竞争核心在不同的套接字上并且还必须跨越套接字互连,则问题将进一步加剧.

我的问题如下.如果要更新的所有变量都是易失性的,为什么这个填充会导致性能提升?我的理解是,volatile变量总是写入并读取到主存储器.因此,我假设在此示例中对任何变量的每次写入和读取都将导致当前核心高速缓存行的刷新.

所以根据我的理解.如果线程1使线程2的高速缓存行无效,那么在它从其自己的高速缓存行读取值之前,这将不会成为线程2的显示器.它读取的值是一个易失性值,因此这有效地使缓存变脏,从而导致从主存储器读取.

我的理解在哪里出错了?

谢谢

Gra*_*ray 6

如果要更新的所有变量都是易失性的,为什么这个填充会导致性能提升?

所以这里有两件事:

  1. 我们正在处理一个VolatileLong对象数组,每个线程都在自己运行VolatileLong.(见private final int arrayIndex).
  2. 每个VolatileLong对象都有一个volatile字段.

volatile访问意味着线程必须都无效缓存"行"保存自己volatile long value,他们需要锁定该高速缓存行进行更新.正如文章所述,缓存行通常约为64字节左右.

文章说通过向VolatileLong对象添加填充,它将每个线程锁定的对象移动到不同的缓存行中.因此,即使不同的线程在分配它们时仍然跨越内存屏障volatile long value,它们也处于不同的缓存线中,因此不会导致过多的L2缓存带宽.

总之,性能提升的原因在于,即使线程仍然锁定其缓存行以更新volatile字段,这些锁现在位于不同的内存块上,因此它们不会与其他线程的锁冲突并导致缓存失效.