java高並發系列 – 第22天:java中底層工具類Unsafe,高手必須要了解

  • 2019 年 10 月 3 日
  • 筆記

這是java高並發系列第22篇文章,文章基於jdk1.8環境。

本文主要內容

  1. 基本介紹
  2. 通過反射獲取Unsafe實例
  3. Unsafe中的CAS操作
  4. Unsafe中原子操作相關方法介紹
  5. Unsafe中執行緒調度相關方法
  6. park和unpark示例
  7. Unsafe鎖示例
  8. Unsafe中保證變數的可見性
  9. Unsafe中Class相關方法
  10. 示例:staticFieldOffset、staticFieldBase、staticFieldBase
  11. 示例:shouldBeInitialized、ensureClassInitialized
  12. 對象操作的其他方法
  13. 繞過構造方法創建對象
  14. 數組相關的一些方法
  15. 記憶體屏障相關操作
  16. java高並發系列目錄

基本介紹

最近我們一直在學習java高並發,java高並發中主要涉及到類位於java.util.concurrent包中,簡稱juc,juc中大部分類都是依賴於Unsafe來實現的,主要用到了Unsafe中的CAS、執行緒掛起、執行緒恢復等相關功能。所以如果打算深入了解JUC原理的,必須先了解一下Unsafe類。

先上一幅Unsafe類的功能圖:

Unsafe是位於sun.misc包下的一個類,主要提供一些用於執行低級別、不安全操作的方法,如直接訪問系統記憶體資源、自主管理記憶體資源等,這些方法在提升Java運行效率、增強Java語言底層資源操作能力方面起到了很大的作用。但由於Unsafe類使Java語言擁有了類似C語言指針一樣操作記憶體空間的能力,這無疑也增加了程式發生相關指針問題的風險。在程式中過度、不正確使用Unsafe類會使得程式出錯的概率變大,使得Java這種安全的語言變得不再「安全」,因此對Unsafe的使用一定要慎重。

從Unsafe功能圖上看出,Unsafe提供的API大致可分為記憶體操作CASClass相關對象操作執行緒調度系統資訊獲取記憶體屏障數組操作等幾類,本文主要介紹3個常用的操作:CAS、執行緒調度、對象操作。

看一下UnSafe的原碼部分:

public final class Unsafe {    // 單例對象    private static final Unsafe theUnsafe;      private Unsafe() {    }    @CallerSensitive    public static Unsafe getUnsafe() {      Class var0 = Reflection.getCallerClass();      // 僅在引導類載入器`BootstrapClassLoader`載入時才合法      if(!VM.isSystemDomainLoader(var0.getClassLoader())) {        throw new SecurityException("Unsafe");      } else {        return theUnsafe;      }    }  }

從程式碼中可以看出,Unsafe類為單例實現,提供靜態方法getUnsafe獲取Unsafe實例,內部會判斷當前調用者是否是由系統類載入器載入的,如果不是系統類載入器載入的,會拋出SecurityException異常。

那我們想使用這個類,如何獲取呢?

可以把我們的類放在jdk的lib目錄下,那麼啟動的時候會自動載入,這種方式不是很好。

我們學過反射,通過反射可以獲取到Unsafe中的theUnsafe欄位的值,這樣可以獲取到Unsafe對象的實例。

通過反射獲取Unsafe實例

程式碼如下:

package com.itsoku.chat21;    import sun.misc.Unsafe;    import java.lang.reflect.Field;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  public class Demo1 {      static Unsafe unsafe;        static {          try {              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);          } catch (Exception e) {              e.printStackTrace();          }      }        public static void main(String[] args) {          System.out.println(unsafe);      }  }

輸出:

sun.misc.Unsafe@76ed5528

Unsafe中的CAS操作

看一下Unsafe中CAS相關方法定義:

/**   * CAS 操作   *   * @param o        包含要修改field的對象   * @param offset   對象中某field的偏移量   * @param expected 期望值   * @param update   更新值   * @return true | false   */  public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);    public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);    public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

什麼是CAS? 即比較並替換,實現並發演算法時常用到的一種技術。CAS操作包含三個操作數——記憶體位置、預期原值及新值執行CAS操作的時候,將記憶體位置的值與預期原值比較,如果相匹配,那麼處理器會自動將該位置值更新為新值,否則,處理器不做任何操作,多個執行緒同時執行cas操作,只有一個會成功。我們都知道,CAS是一條CPU的原子指令(cmpxchg指令),不會造成所謂的數據不一致問題,Unsafe提供的CAS方法(如compareAndSwapXXX)底層實現即為CPU指令cmpxchg。執行cmpxchg指令的時候,會判斷當前系統是否為多核系統,如果是就給匯流排加鎖,只有一個執行緒會對匯流排加鎖成功,加鎖成功之後會執行cas操作,也就是說CAS的原子性實際上是CPU實現的, 其實在這一點上還是有排他鎖的,只是比起用synchronized, 這裡的排他時間要短的多, 所以在多執行緒情況下性能會比較好。

說一下offset,offeset為欄位的偏移量,每個對象有個地址,offset是欄位相對於對象地址的偏移量,對象地址記為baseAddress,欄位偏移量記為offeset,那麼欄位對應的實際地址就是baseAddress+offeset,所以cas通過對象、偏移量就可以去操作欄位對應的值了。

CAS在java.util.concurrent.atomic相關類、Java AQS、JUC中並發集合等實現上有非常廣泛的應用,我們看一下java.util.concurrent.atomic.AtomicInteger類,這個類可以在多執行緒環境中對int類型的數據執行高效的原子修改操作,並保證數據的正確性,看一下此類中用到Unsafe cas的地方:

JUC中其他地方使用到CAS的地方就不列舉了,有興趣的可以去看一下源碼。

Unsafe中原子操作相關方法介紹

5個方法,看一下實現:

/**   * int類型值原子操作,對var2地址對應的值做原子增加操作(增加var4)   *   * @param var1 操作的對象   * @param var2 var2欄位記憶體地址偏移量   * @param var4 需要加的值   * @return   */  public final int getAndAddInt(Object var1, long var2, int var4) {      int var5;      do {          var5 = this.getIntVolatile(var1, var2);      } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));        return var5;  }    /**   * long類型值原子操作,對var2地址對應的值做原子增加操作(增加var4)   *   * @param var1 操作的對象   * @param var2 var2欄位記憶體地址偏移量   * @param var4 需要加的值   * @return 返回舊值   */  public final long getAndAddLong(Object var1, long var2, long var4) {      long var6;      do {          var6 = this.getLongVolatile(var1, var2);      } while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));        return var6;  }    /**   * int類型值原子操作方法,將var2地址對應的值置為var4   *   * @param var1 操作的對象   * @param var2 var2欄位記憶體地址偏移量   * @param var4 新值   * @return 返回舊值   */  public final int getAndSetInt(Object var1, long var2, int var4) {      int var5;      do {          var5 = this.getIntVolatile(var1, var2);      } while (!this.compareAndSwapInt(var1, var2, var5, var4));        return var5;  }    /**   * long類型值原子操作方法,將var2地址對應的值置為var4   *   * @param var1 操作的對象   * @param var2 var2欄位記憶體地址偏移量   * @param var4 新值   * @return 返回舊值   */  public final long getAndSetLong(Object var1, long var2, long var4) {      long var6;      do {          var6 = this.getLongVolatile(var1, var2);      } while (!this.compareAndSwapLong(var1, var2, var6, var4));        return var6;  }    /**   * Object類型值原子操作方法,將var2地址對應的值置為var4   *   * @param var1 操作的對象   * @param var2 var2欄位記憶體地址偏移量   * @param var4 新值   * @return 返回舊值   */  public final Object getAndSetObject(Object var1, long var2, Object var4) {      Object var5;      do {          var5 = this.getObjectVolatile(var1, var2);      } while (!this.compareAndSwapObject(var1, var2, var5, var4));        return var5;  }

看一下上面的方法,內部通過自旋的CAS操作實現的,這些方法都可以保證操作的數據在多執行緒環境中的原子性,正確性。

來個示例,我們還是來實現一個網站計數功能,同時有100個人發起對網站的請求,每個人發起10次請求,每次請求算一次,最終結果是1000次,程式碼如下:

package com.itsoku.chat21;    import sun.misc.Unsafe;    import java.lang.reflect.Field;  import java.util.concurrent.CountDownLatch;  import java.util.concurrent.TimeUnit;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  public class Demo2 {      static Unsafe unsafe;      //用來記錄網站訪問量,每次訪問+1      static int count;      //count在Demo.class對象中的地址偏移量      static long countOffset;        static {          try {              //獲取Unsafe對象              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);                Field countField = Demo2.class.getDeclaredField("count");              //獲取count欄位在Demo2中的記憶體地址的偏移量              countOffset = unsafe.staticFieldOffset(countField);          } catch (Exception e) {              e.printStackTrace();          }      }        //模擬訪問一次      public static void request() throws InterruptedException {          //模擬耗時5毫秒          TimeUnit.MILLISECONDS.sleep(5);          //對count原子加1          unsafe.getAndAddInt(Demo2.class, countOffset, 1);      }        public static void main(String[] args) throws InterruptedException {          long starTime = System.currentTimeMillis();          int threadSize = 100;          CountDownLatch countDownLatch = new CountDownLatch(threadSize);          for (int i = 0; i < threadSize; i++) {              Thread thread = new Thread(() -> {                  try {                      for (int j = 0; j < 10; j++) {                          request();                      }                  } catch (InterruptedException e) {                      e.printStackTrace();                  } finally {                      countDownLatch.countDown();                  }              });              thread.start();          }            countDownLatch.await();          long endTime = System.currentTimeMillis();          System.out.println(Thread.currentThread().getName() + ",耗時:" + (endTime - starTime) + ",count=" + count);      }  }

輸出:

main,耗時:114,count=1000

程式碼中我們在靜態塊中通過反射獲取到了Unsafe類的實例,然後獲取Demo2中count欄位記憶體地址偏移量countOffset,main方法中模擬了100個人,每人發起10次請求,等到所有請求完畢之後,輸出count的結果。

程式碼中用到了CountDownLatch,通過countDownLatch.await()讓主執行緒等待,等待100個子執行緒都執行完畢之後,主執行緒在進行運行。CountDownLatch的使用可以參考:java高並發系列 – 第16天:JUC中等待多執行緒完成的工具類CountDownLatch,必備技能

Unsafe中執行緒調度相關方法

這部分,包括執行緒掛起、恢復、鎖機制等方法。

//取消阻塞執行緒  public native void unpark(Object thread);  //阻塞執行緒,isAbsolute:是否是絕對時間,如果為true,time是一個絕對時間,如果為false,time是一個相對時間,time表示納秒  public native void park(boolean isAbsolute, long time);  //獲得對象鎖(可重入鎖)  @Deprecated  public native void monitorEnter(Object o);  //釋放對象鎖  @Deprecated  public native void monitorExit(Object o);  //嘗試獲取對象鎖  @Deprecated  public native boolean tryMonitorEnter(Object o);

調用park後,執行緒將被阻塞,直到unpark調用或者超時,如果之前調用過unpark,不會進行阻塞,即parkunpark不區分先後順序。monitorEnter、monitorExit、tryMonitorEnter 3個方法已過期,不建議使用了。

park和unpark示例

程式碼如下:

package com.itsoku.chat21;    import sun.misc.Unsafe;    import java.lang.reflect.Field;  import java.util.concurrent.TimeUnit;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  public class Demo3 {      static Unsafe unsafe;        static {          try {              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);          } catch (Exception e) {              e.printStackTrace();          }      }        /**       * 調用park和unpark,模擬執行緒的掛起和喚醒       *       * @throws InterruptedException       */      public static void m1() throws InterruptedException {          Thread thread = new Thread(() -> {              System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",start");              unsafe.park(false, 0);              System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",end");          });          thread.setName("thread1");          thread.start();            TimeUnit.SECONDS.sleep(5);          unsafe.unpark(thread);      }        /**       * 阻塞指定的時間       */      public static void m2() {          Thread thread = new Thread(() -> {              System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",start");              //執行緒掛起3秒              unsafe.park(false, TimeUnit.SECONDS.toNanos(3));              System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",end");          });          thread.setName("thread2");          thread.start();      }        public static void main(String[] args) throws InterruptedException {          m1();          m2();      }  }

輸出:

1565000238474,thread1,start  1565000243475,thread1,end  1565000243475,thread2,start  1565000246476,thread2,end

m1()中thread1調用park方法,park方法會將當前執行緒阻塞,被阻塞了5秒之後,被主執行緒調用unpark方法給喚醒了,unpark方法參數表示需要喚醒的執行緒。

執行緒中相當於有個許可,許可默認是0,調用park的時候,發現是0會阻塞當前執行緒,調用unpark之後,許可會被置為1,並會喚醒當前執行緒。如果在park之前先調用了unpark方法,執行park方法的時候,不會阻塞。park方法被喚醒之後,許可又會被置為0。多次調用unpark的效果是一樣的,許可還是1。

juc中的LockSupport類是通過unpark和park方法實現的,需要了解LockSupport可以移步:JUC中的LockSupport工具類

Unsafe鎖示例

程式碼如下:

package com.itsoku.chat21;    import sun.misc.Unsafe;    import java.lang.reflect.Field;  import java.util.concurrent.CountDownLatch;  import java.util.concurrent.TimeUnit;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  public class Demo4 {        static Unsafe unsafe;      //用來記錄網站訪問量,每次訪問+1      static int count;        static {          try {              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);          } catch (Exception e) {              e.printStackTrace();          }      }        //模擬訪問一次      public static void request() {          unsafe.monitorEnter(Demo4.class);          try {              count++;          } finally {              unsafe.monitorExit(Demo4.class);          }      }          public static void main(String[] args) throws InterruptedException {          long starTime = System.currentTimeMillis();          int threadSize = 100;          CountDownLatch countDownLatch = new CountDownLatch(threadSize);          for (int i = 0; i < threadSize; i++) {              Thread thread = new Thread(() -> {                  try {                      for (int j = 0; j < 10; j++) {                          request();                      }                  } finally {                      countDownLatch.countDown();                  }              });              thread.start();          }            countDownLatch.await();          long endTime = System.currentTimeMillis();          System.out.println(Thread.currentThread().getName() + ",耗時:" + (endTime - starTime) + ",count=" + count);      }  }  

輸出:

main,耗時:64,count=1000

monitorEnter、monitorExit都有1個參數,表示上鎖的對象。用法和synchronized關鍵字語義類似。

注意:

  1. monitorEnter、monitorExit、tryMonitorEnter 3個方法已過期,不建議使用了
  2. monitorEnter、monitorExit必須成對出現,出現的次數必須一致,也就是說鎖了n次,也必須釋放n次,否則會造成死鎖

Unsafe中保證變數的可見性

關於變數可見性需要先了解java記憶體模型JMM,可以移步到:

JMM相關的一些概念

volatile與Java記憶體模型

java中操作記憶體分為主記憶體和工作記憶體,共享數據在主記憶體中,執行緒如果需要操作主記憶體的數據,需要先將主記憶體的數據複製到執行緒獨有的工作記憶體中,操作完成之後再將其刷新到主記憶體中。如執行緒A要想看到執行緒B修改後的數據,需要滿足:執行緒B修改數據之後,需要將數據從自己的工作記憶體中刷新到主記憶體中,並且A需要去主記憶體中讀取數據。

被關鍵字volatile修飾的數據,有2點語義:

  1. 如果一個變數被volatile修飾,讀取這個變數時候,會強制從主記憶體中讀取,然後將其複製到當前執行緒的工作記憶體中使用
  2. 給volatile修飾的變數賦值的時候,會強制將賦值的結果從工作記憶體刷新到主記憶體

上面2點語義保證了被volatile修飾的數據在多執行緒中的可見性。

Unsafe中提供了和volatile語義一樣的功能的方法,如下:

//設置給定對象的int值,使用volatile語義,即設置後立馬更新到記憶體對其他執行緒可見  public native void  putIntVolatile(Object o, long offset, int x);  //獲得給定對象的指定偏移量offset的int值,使用volatile語義,總能獲取到最新的int值。  public native int getIntVolatile(Object o, long offset);

putIntVolatile方法,2個參數:

o:表示需要操作的對象

offset:表示操作對象中的某個欄位地址偏移量

x:將offset對應的欄位的值修改為x,並且立即刷新到主存中

調用這個方法,會強制將工作記憶體中修改的數據刷新到主記憶體中。

getIntVolatile方法,2個參數

o:表示需要操作的對象

offset:表示操作對象中的某個欄位地址偏移量

每次調用這個方法都會強制從主記憶體讀取值,將其複製到工作記憶體中使用。

其他的還有幾個putXXXVolatile、getXXXVolatile方法和上面2個類似。

本文主要講解這些內容,希望您能有所收穫,謝謝。

Unsafe中Class相關方法

此部分主要提供Class和它的靜態欄位的操作相關方法,包含靜態欄位記憶體定位、定義類、定義匿名類、檢驗&確保初始化等。

//獲取給定靜態欄位的記憶體地址偏移量,這個值對於給定的欄位是唯一且固定不變的  public native long staticFieldOffset(Field f);  //獲取一個靜態類中給定欄位的對象指針  public native Object staticFieldBase(Field f);  //判斷是否需要初始化一個類,通常在獲取一個類的靜態屬性的時候(因為一個類如果沒初始化,它的靜態屬性也不會初始化)使用。 當且僅當ensureClassInitialized方法不生效時返回false。  public native boolean shouldBeInitialized(Class<?> c);  //檢測給定的類是否已經初始化。通常在獲取一個類的靜態屬性的時候(因為一個類如果沒初始化,它的靜態屬性也不會初始化)使用。  public native void ensureClassInitialized(Class<?> c);  //定義一個類,此方法會跳過JVM的所有安全檢查,默認情況下,ClassLoader(類載入器)和ProtectionDomain(保護域)實例來源於調用者  public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);  //定義一個匿名類  public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

示例:staticFieldOffset、staticFieldBase、staticFieldBase

package com.itsoku.chat21;    import lombok.extern.slf4j.Slf4j;  import sun.misc.Unsafe;    import java.lang.reflect.Field;  import java.util.Arrays;  import java.util.List;  import java.util.concurrent.TimeUnit;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  @Slf4j  public class Demo7 {        static Unsafe unsafe;      //靜態屬性      private static Object v1;      //實例屬性      private Object v2;        static {          //獲取Unsafe對象          try {              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);          } catch (Exception e) {              e.printStackTrace();          }      }        public static void main(String[] args) throws NoSuchFieldException {          Field v1Field = Demo7.class.getDeclaredField("v1");          Field v2Field = Demo7.class.getDeclaredField("v2");            System.out.println(unsafe.staticFieldOffset(v1Field));          System.out.println(unsafe.objectFieldOffset(v2Field));            System.out.println(unsafe.staticFieldBase(v1Field)==Demo7.class);      }  }

輸出:

112  12  true

可以看出staticFieldBase返回的就是Demo2的class對象。

示例:shouldBeInitialized、ensureClassInitialized

package com.itsoku.chat21;    import lombok.extern.slf4j.Slf4j;  import sun.misc.Unsafe;    import java.lang.reflect.Field;  import java.util.concurrent.TimeUnit;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  public class Demo8 {        static Unsafe unsafe;        static {          //獲取Unsafe對象          try {              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);          } catch (Exception e) {              e.printStackTrace();          }      }        static class C1 {          private static int count;            static {              count = 10;              System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",C1 static init.");          }      }        static class C2 {          private static int count;            static {              count = 11;              System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",C2 static init.");          }      }        public static void main(String[] args) throws NoSuchFieldException {          //判斷C1類是需要需要初始化,如果已經初始化了,會返回false,如果此類沒有被初始化過,返回true          if (unsafe.shouldBeInitialized(C1.class)) {              System.out.println("C1需要進行初始化");              //對C1進行初始化              unsafe.ensureClassInitialized(C1.class);          }            System.out.println(C2.count);          System.out.println(unsafe.shouldBeInitialized(C1.class));      }  }

輸出:

C1需要進行初始化  1565069660679,main,C1 static init.  1565069660680,main,C2 static init.  11  false

程式碼中C1未被初始化過,所以unsafe.shouldBeInitialized(C1.class)返回true,然後調用unsafe.ensureClassInitialized(C1.class)進行初始化。

程式碼中執行C2.count會觸發C2進行初始化,所以shouldBeInitialized(C1.class)返回false

對象操作的其他方法

//返回對象成員屬性在記憶體地址相對於此對象的記憶體地址的偏移量  public native long objectFieldOffset(Field f);  //獲得給定對象的指定地址偏移量的值,與此類似操作還有:getInt,getDouble,getLong,getChar等  public native Object getObject(Object o, long offset);  //給定對象的指定地址偏移量設值,與此類似操作還有:putInt,putDouble,putLong,putChar等  public native void putObject(Object o, long offset, Object x);  //從對象的指定偏移量處獲取變數的引用,使用volatile的載入語義  public native Object getObjectVolatile(Object o, long offset);  //存儲變數的引用到對象的指定的偏移量處,使用volatile的存儲語義  public native void putObjectVolatile(Object o, long offset, Object x);  //有序、延遲版本的putObjectVolatile方法,不保證值的改變被其他執行緒立即看到,只有在field被volatile修飾符修飾時有效  public native void putOrderedObject(Object o, long offset, Object x);  //繞過構造方法、初始化程式碼來創建對象  public native Object allocateInstance(Class<?> cls) throws InstantiationException;

getObject相當於獲取對象中欄位的值,putObject相當於給欄位賦值,有興趣的可以自己寫個例子看看效果。

繞過構造方法創建對象

介紹一下allocateInstance,這個方法可以繞過構造方法來創建對象,示例程式碼如下:

package com.itsoku.chat21;    import sun.misc.Unsafe;    import java.lang.reflect.Field;    /**   * 跟著阿里p7學並發,微信公眾號:javacode2018   */  public class Demo9 {        static Unsafe unsafe;        static {          //獲取Unsafe對象          try {              Field field = Unsafe.class.getDeclaredField("theUnsafe");              field.setAccessible(true);              unsafe = (Unsafe) field.get(null);          } catch (Exception e) {              e.printStackTrace();          }      }        static class C1 {          private String name;            private C1() {              System.out.println("C1 default constructor!");          }            private C1(String name) {              this.name = name;              System.out.println("C1 有參 constructor!");          }      }        public static void main(String[] args) throws InstantiationException {          System.out.println(unsafe.allocateInstance(C1.class));      }  }

輸出:

com.itsoku.chat21.Demo9$C1@782830e

看一下類C1中有兩個構造方法,都是private的,通過new、反射的方式都無法創建對象。但是可以通過Unsafe的allocateInstance方法繞過構造函數來創建C1的實例,輸出的結果中可以看出創建成功了,並且沒有調用構造方法。

典型應用

  • 常規對象實例化方式:我們通常所用到的創建對象的方式,從本質上來講,都是通過new機制來實現對象的創建。但是,new機制有個特點就是當類只提供有參的構造函數且無顯示聲明無參構造函數時,則必須使用有參構造函數進行對象構造,而使用有參構造函數時,必須傳遞相應個數的參數才能完成對象實例化。
  • 非常規的實例化方式:而Unsafe中提供allocateInstance方法,僅通過Class對象就可以創建此類的實例對象,而且不需要調用其構造函數、初始化程式碼、JVM安全檢查等。它抑制修飾符檢測,也就是即使構造器是private修飾的也能通過此方法實例化,只需提類對象即可創建相應的對象。由於這種特性,allocateInstance在java.lang.invoke、Objenesis(提供繞過類構造器的對象生成方式)、Gson(反序列化時用到)中都有相應的應用。

數組相關的一些方法

這部分主要介紹與數據操作相關的arrayBaseOffset與arrayIndexScale這兩個方法,兩者配合起來使用,即可定位數組中每個元素在記憶體中的位置。

//返回數組中第一個元素的偏移地址  public native int arrayBaseOffset(Class<?> arrayClass);  //返回數組中一個元素佔用的大小  public native int arrayIndexScale(Class<?> arrayClass);

這兩個與數據操作相關的方法,在java.util.concurrent.atomic 包下的AtomicIntegerArray(可以實現對Integer數組中每個元素的原子性操作)中有典型的應用,如下圖AtomicIntegerArray源碼所示,通過Unsafe的arrayBaseOffset、arrayIndexScale分別獲取數組首元素的偏移地址base及單個元素大小因子scale。後續相關原子性操作,均依賴於這兩個值進行數組中元素的定位,如下圖二所示的getAndAdd方法即通過checkedByteOffset方法獲取某數組元素的偏移地址,而後通過CAS實現原子性操作。

數組元素定位:

Unsafe類中有很多以BASE_OFFSET結尾的常量,比如ARRAY_INT_BASE_OFFSET,ARRAY_BYTE_BASE_OFFSET等,這些常量值是通過arrayBaseOffset方法得到的。arrayBaseOffset方法是一個本地方法,可以獲取數組第一個元素的偏移地址。Unsafe類中還有很多以INDEX_SCALE結尾的常量,比如 ARRAY_INT_INDEX_SCALE , ARRAY_BYTE_INDEX_SCALE等,這些常量值是通過arrayIndexScale方法得到的。arrayIndexScale方法也是一個本地方法,可以獲取數組的轉換因子,也就是數組中元素的增量地址。將arrayBaseOffset與arrayIndexScale配合使用,可以定位數組中每個元素在記憶體中的位置。

記憶體屏障相關操作

在Java 8中引入,用於定義記憶體屏障(也稱記憶體柵欄,記憶體柵障,屏障指令等,是一類同步屏障指令,是CPU或編譯器在對記憶體隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行後才可以開始執行此點之後的操作),避免程式碼重排序。

//記憶體屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障後,屏障後的load操作不能被重排序到屏障前  public native void loadFence();  //記憶體屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障後,屏障後的store操作不能被重排序到屏障前  public native void storeFence();  //記憶體屏障,禁止load、store操作重排序  public native void fullFence();

Unsafe相關的就介紹這麼多!

java高並發系列目錄

  1. java高並發系列 – 第1天:必須知道的幾個概念
  2. java高並發系列 – 第2天:並發級別
  3. java高並發系列 – 第3天:有關並行的兩個重要定律
  4. java高並發系列 – 第4天:JMM相關的一些概念
  5. java高並發系列 – 第5天:深入理解進程和執行緒
  6. java高並發系列 – 第6天:執行緒的基本操作
  7. java高並發系列 – 第7天:volatile與Java記憶體模型
  8. java高並發系列 – 第8天:執行緒組
  9. java高並發系列 – 第9天:用戶執行緒和守護執行緒
  10. java高並發系列 – 第10天:執行緒安全和synchronized關鍵字
  11. java高並發系列 – 第11天:執行緒中斷的幾種方式
  12. java高並發系列 – 第12天JUC:ReentrantLock重入鎖
  13. java高並發系列 – 第13天:JUC中的Condition對象
  14. java高並發系列 – 第14天:JUC中的LockSupport工具類,必備技能
  15. java高並發系列 – 第15天:JUC中的Semaphore(訊號量)
  16. java高並發系列 – 第16天:JUC中等待多執行緒完成的工具類CountDownLatch,必備技能
  17. java高並發系列 – 第17天:JUC中的循環柵欄CyclicBarrier的6種使用場景
  18. java高並發系列 – 第18天:JAVA執行緒池,這一篇就夠了
  19. java高並發系列 – 第19天:JUC中的Executor框架詳解1
  20. java高並發系列 – 第20天:JUC中的Executor框架詳解2
  21. java高並發系列 – 第21天:java中的CAS,你需要知道的東西

java高並發系列連載中,總計估計會有四五十篇文章。

阿里p7一起學並發,公眾號:路人甲java,每天獲取最新文章!