volatile的用途

1.线程可见性

可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

 

package com.msb.testvolatile;
​
public class T01_ThreadVisibility {
   private static volatile boolean flag = true;
​
   public static void main(String[] args) throws InterruptedException {
       new Thread(()-> {
           while (flag) {
               //do sth
          }
           System.out.println("end");
      }, "server").start();
​
​
       Thread.sleep(1000);
​
       flag = false;
  }
}

2.防止指令重排序

问题:DCL单例需不需要加volatile?

CPU的基础知识

  • 缓存行对齐缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高Disruptor

    package com.msb.juc.c_028_FalseSharing;
    ​
    public class T02_CacheLinePadding {
       private static class Padding {
           public volatile long p1, p2, p3, p4, p5, p6, p7; //
      }
    ​
       private static class T extends Padding {
           public volatile long x = 0L;
      }
    ​
       public static T[] arr = new T[2];
    ​
       static {
           arr[0] = new T();
           arr[1] = new T();
      }
    ​
       public static void main(String[] args) throws Exception {
           Thread t1 = new Thread(()->{
               for (long i = 0; i < 1000_0000L; i++) {
                   arr[0].x = i;
              }
          });
    ​
           Thread t2 = new Thread(()->{
               for (long i = 0; i < 1000_0000L; i++) {
                   arr[1].x = i;
              }
          });
    ​
           final long start = System.nanoTime();
           t1.start();
           t2.start();
           t1.join();
           t2.join();
           System.out.println((System.nanoTime() - start)/100_0000);
      }
    }

    MESI

  • 伪共享

  • 合并写CPU内部的4个字节的Buffer

    package com.msb.juc.c_029_WriteCombining;
    ​
    public final class WriteCombining {
    ​
       private static final int ITERATIONS = Integer.MAX_VALUE;
       private static final int ITEMS = 1 << 24;
       private static final int MASK = ITEMS - 1;
    ​
       private static final byte[] arrayA = new byte[ITEMS];
       private static final byte[] arrayB = new byte[ITEMS];
       private static final byte[] arrayC = new byte[ITEMS];
       private static final byte[] arrayD = new byte[ITEMS];
       private static final byte[] arrayE = new byte[ITEMS];
       private static final byte[] arrayF = new byte[ITEMS];
    ​
       public static void main(final String[] args) {
    ​
           for (int i = 1; i <= 3; i++) {
               System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
               System.out.println(i + " SplitLoop duration (ns) = " + runCaseTwo());
          }
      }
    ​
       public static long runCaseOne() {
           long start = System.nanoTime();
           int i = ITERATIONS;
    ​
           while (--i != 0) {
               int slot = i & MASK;
               byte b = (byte) i;
               arrayA[slot] = b;
               arrayB[slot] = b;
               arrayC[slot] = b;
               arrayD[slot] = b;
               arrayE[slot] = b;
               arrayF[slot] = b;
          }
           return System.nanoTime() - start;
      }
    ​
       public static long runCaseTwo() {
           long start = System.nanoTime();
           int i = ITERATIONS;
           while (--i != 0) {
               int slot = i & MASK;
               byte b = (byte) i;
               arrayA[slot] = b;
               arrayB[slot] = b;
               arrayC[slot] = b;
          }
           i = ITERATIONS;
           while (--i != 0) {
               int slot = i & MASK;
               byte b = (byte) i;
               arrayD[slot] = b;
               arrayE[slot] = b;
               arrayF[slot] = b;
          }
           return System.nanoTime() - start;
      }
    }

     

  • 指令重排序

    什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理

    package com.msb.jvm.c3_jmm;
    ​
    public class T04_Disorder {
       private static int x = 0, y = 0;
       private static int a = 0, b =0;
    ​
       public static void main(String[] args) throws InterruptedException {
           int i = 0;
           for(;;) {
               i++;
               x = 0; y = 0;
               a = 0; b = 0;
               Thread one = new Thread(new Runnable() {
                   public void run() {
                       //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
                       //shortWait(100000);
                       a = 1;
                       x = b;
                  }
              });
    ​
               Thread other = new Thread(new Runnable() {
                   public void run() {
                       b = 1;
                       y = a;
                  }
              });
               one.start();other.start();
               one.join();other.join();
               String result = "第" + i + "次 (" + x + "," + y + ")";
               if(x == 0 && y == 0) {
                   System.err.println(result);
                   break;
              } else {
                   //System.out.println(result);
              }
          }
      }
    ​
    ​
       public static void shortWait(long interval){
           long start = System.nanoTime();
           long end;
           do{
               end = System.nanoTime();
          }while(start + interval >= end);
      }
    }

     

volatile如何解决指令重排序

1: volatile i

2: ACC_VOLATILE

3: JVM的内存屏障

4:hotspot实现

bytecodeinterpreter.cpp

int field_offset = cache->f2_as_index();
         if (cache->is_volatile()) {
           if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
             OrderAccess::fence();
          }

orderaccess_linux_x86.inline.hpp

inline void OrderAccess::fence() {
 if (os::is_MP()) {
   // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
   __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
   __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}

第一次写这种底层的东西,整理了好几次话术,实在是没整明白该怎么发,因为确实有点难以整理话术,所以这里附上了关键地方的源码以及实例,运行一下可能会更好理解这些技术,后面我会去逐步改进