從JDK原始碼角度看併發的原子性如何保證

超人汪小建發表於2019-02-22

JDK原始碼中,在研究AQS框架時,會發現很多地方都使用了CAS操作,在併發實現中CAS操作必須具備原子性,而且是硬體級別的原子性,Java被隔離在硬體之上,明顯力不從心,這時為了能直接操作作業系統層面,肯定要通過用C++編寫的native本地方法來擴充套件實現。JDK提供了一個類來滿足CAS的要求,sun.misc.Unsafe,從名字上可以大概知道它用於執行低階別、不安全的操作,AQS就是使用此類完成硬體級別的原子操作。

Unsafe是一個很強大的類,它可以分配記憶體、釋放記憶體、可以定位物件某欄位的位置、可以修改物件的欄位值、可以使執行緒掛起、使執行緒恢復、可進行硬體級別原子的CAS操作等等,但平時我們沒有這麼特殊的需求去使用它,而且必須在受信任程式碼(一般由JVM指定)中呼叫此類,例如直接Unsafe unsafe = Unsafe.getUnsafe();獲取一個Unsafe例項是不會成功的,因為這個類的安全性很重要,設計者對其進行了如下判斷,它會檢測呼叫它的類是否由啟動類載入器Bootstrap ClassLoader(它的類載入器為null)載入,由此保證此類只能由JVM指定的類使用。

public static Unsafe getUnsafe() {  
   Class cc = sun.reflect.Reflection.getCallerClass(2);  
   if (cc.getClassLoader() != null)  
       throw new SecurityException("Unsafe");  
   return theUnsafe;  
}複製程式碼

當然可以通過反射繞過上面的限制,用下面的getUnsafeInstance方法可以獲取Unsafe例項,這段程式碼演示瞭如何獲取java物件的相對地址偏移量及使用Unsafe完成CAS操作,最終輸出的是flag欄位的記憶體偏移量及CAS操作後的值。分別為8和101。另外如果使用開發工具如Eclipse,可能會編譯通不過,只要把編譯錯誤提示關掉即可。

public class UnsafeTest {  
private int flag = 100;  
private static long offset;  
private static Unsafe unsafe = null;  
static{  
     try{  
          unsafe= getUnsafeInstance();  
          offset= unsafe.objectFieldOffset(UnsafeTest.class  
                   .getDeclaredField("flag"));  
     }catch (Exception e) {  
          e.printStackTrace();  
     }  
}  

public static void main(String[] args) throws Exception {  
     int expect = 100;  
     int update = 101;  
     UnsafeTest unsafeTest = new UnsafeTest();  
     System.out.println("unsafeTest物件的flag欄位的地址偏移量為:"+offset);  
     unsafeTest.doSwap(offset,expect, update);  
     System.out.println("CAS操作後的flag值為:" +unsafeTest.getFlag());  
}  

privateboolean doSwap(long offset, int expect, int update) {  
     returnunsafe.compareAndSwapInt(this, offset, expect, update);  
}  

publicint getFlag() {  
     returnflag;  
}  

private static Unsafe getUnsafeInstance() throws SecurityException,  
          NoSuchFieldException,IllegalArgumentException,  
          IllegalAccessException{  
     Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");  
     theUnsafeInstance.setAccessible(true);  
     return (Unsafe)theUnsafeInstance.get(Unsafe.class);  
}  
}複製程式碼

Unsafe類讓我們明白了java是如何實現對作業系統操作的,一般我們使用java是不需要在記憶體中處理java物件及記憶體地址位置的,但有的時候我們確實需要知道java物件相關的地址,於是我們使用Unsafe類,儘管java對其提供了足夠的安全管理。

Java語言的設計者們極力隱藏涉及底層作業系統的相關操作,但此節我們本著對AQS框架實現的目的,不得不剖析了Unsafe類,因為AQS裡面即是使用Unsafe獲取物件欄位的地址偏移量、相關原子操作來實現CAS操作的。

以下是廣告相關閱讀

========廣告時間========

鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。

為什麼寫《Tomcat核心設計剖析》

=========================

相關閱讀:

從JDK原始碼角度看Object

談談Java基礎資料型別

從JDK原始碼角度看併發鎖的優化

從JDK原始碼角度看執行緒的阻塞和喚醒

從JDK原始碼角度看併發競爭的超時

從JDK原始碼角度看java併發執行緒的中斷

從JDK原始碼角度看Java併發的公平性

從JDK原始碼角度看java併發的原子性如何保證

從JDK原始碼角度看Byte

從JDK原始碼角度看Boolean

從JDK原始碼角度看Short

歡迎關注:

這裡寫圖片描述
這裡寫圖片描述

相關文章