Java高併發11-僞共享,getUnsafe源碼解析並利用反射獲取Unsafe實例

一、複習

  • public native long getLongvolatile(Object obj,long offset)
  • public native long putLongvolatile(Object obj,long offset,long value)
  • void putOrderedLong(Object obj,long offset,long value)
  • void park(boolean isAbsolute,long time)
  • void unpark(Thread thread)
  • long getAndSwapLong(Object obj,long offset,long update)
  • long getAndAddLong(Object obj,long offset,long addValue)
  • Unsafe使用靜態方法getUnsafe()舉例

二、getUnsafe源碼解析

private static final Unsafe unsafe = new Unsafe();
public static Unsafe getUsafe(){
 Class localClass = Reflection.getCallerClass();
 if(!VM.isSystemDomainLoader(localClass.getClassLoader())){
  throw new SecurityException("Unsafe");
 }
 return theUnsafe;
}
public static boolean isSystemDomainLoader(ClassLoader paramClassLoader){
 return paramClassLoader == null;
}
  • 首先調用了getUnsafe這個方法的對象的Class對象,這裏就是TestUnsafe.class
  • 然後判斷是不是Bootstrap類加載器的加載的localClass,在這裏是看是不是Bootstrap加載器加載了TestUnsafe.class.很明顯由於TestUnSafe.class是使用AppClassLoader加載的,所以這裏直接拋出了異常。
  • 爲什麼需要這個判斷?
  • 我們知道Unsafe類是rt.jar包提供的,rt.jar包裏面的類是使用Bootstrap類加載器加載的,所以在main函數中加載Unsafe類的時候,根據委託機制,會委託給Bootstrap去加載Unsafe類。如果沒有這個判斷,那麼我們的應用程序就可以隨意使用Unsafe做事情了,而Unsafe類可以直接操作內存,這是不安全的。(想一想native方法)。所以JDK開發組特意做了這個判斷,讓開發人員在正規渠道使用Unsafe類,而是在rt.jar包裏面的核心類中使用Unsafe功能。

三、使用萬能的反射來獲取Unsafe實例

package com.ruigege.OtherFoundationOfConcurrent2;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
//import jdk.internal.misc.Unsafe;

public class TestUnsafe2 {
 static final Unsafe unsafe;
 static final long offset;
 private volatile long state=0;
 static {
  try {
   //使用反射獲取成員變量theState的值
   Field field = Unsafe.class.getDeclaredField("unsafe");
   //設置域值爲可存取
   field.setAccessible(true);
   //獲取該變量field的值
   Unsafe unsafe = (Unsafe)field.get(null);
   //獲得的這個unsafe類,我們使用它的方法來獲取這個測試類的state變量的偏移量
   offset = unsafe.objectFieldOffset(TestUnsafe2.class.getDeclaredField("state"));
   
  }catch(Exception e) {
   e.printStackTrace();
   throw new Error(e);
  }
 }
 
 public static void main(String[] args) {
  TestUnsafe2 testUnsafe2 = new TestUnsafe2();
  boolean success = unsafe.compareAndSwapInt(testUnsafe2,offset,0,1);
  System.out.println(success);
 }
}
  • 如上基本在註釋中都解釋清楚了。

四、Java指令重排

  • Java在會對指令重新排列,如果兩個語句之間沒有依賴,那麼執行哪個在先都行,這種機制在單線程是沒有問題的,但是在多線程中會出現問題,他們之間的值相互依賴,就會出現錯誤的情況
  • 對變量是volatile,會避免重排序和內存可見性問題。
  • 寫volatile變量時,可以保證volatile寫之前的操作不會被編譯器重排序到volatile寫之後。讀取volatile變量的時候,可以確保volatile讀之後的操作不會被編譯器重新排序到volatile讀之前

五、僞共享

1.什麼是cache

  • 在CPU內部會有一二級緩存,它們的存在是因爲比直接讀取內存更加快捷

2.cache內部如何存儲

  • cache內部是按行存儲的,其中每一行稱爲一個cache行。當程序訪問變量的時候,會首先去緩存內尋找變量,如果沒有找到,那麼就去內存中找到這個變量並且把變量的緩存行大小的內存內容都會拿來放到緩存行中。

3.什麼叫僞共享

  • 一個線程是按照cache行來訪問的,因此如果多個變量放到了同一個cache行,並且此時多個線程訪問這行裏的不同變量,因此線程只能排隊修改或取得,我們稱之爲僞共享。

4.出現的問題

  • CPU1裏一級緩存同一cache行有x,y兩個變量;CPU2裏一級緩存同一cache行也有x,y兩個變量
  • 此時CPU1修改了x,因此CPU2根據緩存一致性原則,自己一級緩存x失效,y也跟着訪問不了,因此CPU2無論修改x還是y都需要去內存重新獲取。
  • 這說明了多個線程不可能同時去修改自己所使用的CPU中相同緩存行裏面的變量。

5.舉個例子

int[][] a = new int[1000][1000];
for(int i=0;i<1000;i++){
 for(int j=0;j<1000;j++){
  a[i][j]=i+j;
 }
}

int[][] b = new int[1000][1000];
for(int i=0;i<1000;i++){
 for(int j=0;j<1000;j++){
  b[j][i]=i+j;
 }
}
  • 這兩個循環賦值,上面的會明顯快於下面的,因爲數組就是按行存儲,上面的就是一行一行賦值的,下面的按列賦值,因此每一個賦值,cache都得獲取一個cache行,不同的行來回切換,因此會慢一些

六 、源碼:

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