多線程問的太深入不知道怎麼回答,從 volatile 開始給你講清楚

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

}

}

第一次寫這種底層的東西,整理了好幾次話術,實在是沒整明白該怎麼發,因爲確實有點難以整理話術,所以這裏附上了關鍵地方的源碼以及實例,運行一下可能會更好理解這些技術,後面我會去逐步改進

個人公衆號:Java架構師聯盟,每日更新技術好文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章