一、複習
- 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;
public class TestUnsafe2 {
static final Unsafe unsafe;
static final long offset;
private volatile long state=0;
static {
try {
Field field = Unsafe.class.getDeclaredField("unsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get(null);
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行,不同的行來回切換,因此會慢一些
六 、源碼:
- 所在包:com.ruigege.OtherFoundationOfConcurrent2
https://github.com/ruigege66/ConcurrentJava
- 歡迎關注微信公衆號:傅里葉變換,個人賬號,僅用於技術交流