美文网首页
Java关键字之volatile

Java关键字之volatile

作者: 小丸子的呆地 | 来源:发表于2021-07-12 08:44 被阅读0次

    内容大部分摘自马士兵教育

    volatile用来修饰变量,保证其内存可见性,并且防止jvm或者cpu对其进行指令重排序。

    volatile的用途

    1.线程可见性

    package com.mashibing.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

    • 需要注意,JDK8引入了@sun.misc.Contended注解,来保证缓存行隔离效果
      要使用此注解,必须去掉限制参数:-XX:-RestrictContended

    • 另外,java编译器或者JIT编译器有可能会去除没用的字段,所以填充字段必须加上volatile

    package com.mashibing.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.mashibing.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;
          }
      }
      
      
    • 指令重排序

      package com.mashibing.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);
          }
      }
      

    系统底层如何实现数据一致性

    1. MESI如果能解决,就使用MESI
    2. 如果不能,就锁总线

    系统底层如何保证有序性

    1. 内存屏障sfence mfence lfence等系统原语
    2. 锁总线

    volatile如何解决指令重排序

    1: volatile i

    2: ACC_VOLATILE

    3: JVM的内存屏障

    屏障两边的指令不可以重排!保障有序!
    
    happends-before 
    
    as - if - serial
    

    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
      }
    }
    

    LOCK 用于在多处理器中执行指令时对共享内存的独占使用。
    它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。

    另外还提供了有序的指令无法越过这个内存屏障的作用。

    相关文章

      网友评论

          本文标题:Java关键字之volatile

          本文链接:https://www.haomeiwen.com/subject/dacrpltx.html