14.JDK底层Unsafe类是个啥东西?
老王:小陳啊,從今天開始我們就要進入《結丹篇》了,在這一篇章里面,要注意聽講啊,對后面的每一個階段的理解來說都至關重要的......
小陳:好的,老王,前面的《筑基》、《練氣》兩篇我已經重復看了好幾遍了,早就期待進入下一個境界了......
老王:好,既然你這么有信心,那我們就正式進入主題了。
老王:首先啊,這一篇我們需要從JDK底層的unsafe開始講起,后面的篇章很多都是依賴于unsafe提供的操作的。
JDK底層的unsafe是什么?
小陳:哦,unsafe?unsafe是個啥東西啊?我還沒有接觸過
老王:說起unsafe啊,是JDK提供的一個工具類,unsafe里面的方法大多是native方法,你可以理解為unsafe類是JDK給你提供的一個直接調用操作系統底層功能的一個工具類,unsafe提供了非常多操作系統級別的方法。
(1)比如說通過unsafe可以讓操作系統直接給你分配內存、釋放內存。
(2)突破java語法本身的限制,直接從內存級別去操作堆里面的某個對象的數據;
(3)調用操作系統的CAS指令,實現CAS的功能
(4)操作系統層次將線程掛起和恢復
(5)提供操作系統級別的內存屏障(之前說過的Load屏障和Store屏障),讀取數據強制走主存,修改數據直接刷新到主存
總之unsafe就相當于JDK給你提供的一個直接跟操作系統打交道的一個工具類,通過unsafe可做一些非常底層的指令和行為。
小陳:額,竟然可以直接通過unsafe分配內存,那豈不是不需要通過堆內存也可以直接分配內存了嗎?這樣豈不是很危險,萬一使用者分配大量的內存,沒有及時回收,豈不是很容易造成內存溢出的風險?又或者分配了內存,但是忘記回收了,容易造成內存泄露啊
老王:是啊,unsafe提供了很多操作系統級別的方法,在提供使用者便利的同時,也是隱藏著很多風險的。
但是unsafe提供的這些操作系統級別的方法對于JDK底層的一些工具類、上層的一些框架來說在實現層方便了許多。
比如著名的并發基礎工具類AQS底層就是通過unsafe提供的CAS操作來進行加鎖的,加鎖失敗的線程又是通過unsafe提供的park、unpark操作將線程掛起和喚醒的,還有一些非常著名的開源框架比如netty分配直接內存的方式底層也還是通過unsafe分配直接內存。
老王:所以啊,去了解一下unsafe底層的一些操作還是很有必要的,對于后面我們要學習的很多線程安全的類,比如Atomic系列的類、基于AQS一些列的同步工具還是很有必要的,因為這些底層都是通過unsafe提供的操作去實現的。
小陳:哦哦,原來unsafe類這么重要啊......
老王:下面啊,我就分幾類將一些unsafe提供的一些重要功能
unsafe直接分配和釋放內存
// 分配bytes大小的堆外內存 public native long allocateMemory(long bytes); // 還可以執行從address處開始分配,分配bytes大小的堆外內存 public native long reallocateMemory(long address, long bytes); // 釋放allocateMemory和reallocateMemory申請的內存塊 public native void freeMemory(long address); // 將指定對象的給定offset偏移量內存塊中的所有字節設置為固定值 // 相當于直接讓你在內存級別直接給這個對象的變量賦值了 public native void setMemory(Object o, long offset, long bytes, byte value);// 設置給定內存地址的long值 // 相當于直接在內存級別給address后面的8個字節賦值 public native void putLong(long address, long x); // 獲取指定內存地址的long值 // 相當于獲取address后面的8個字節的值,然后轉化為十進制的long值給你 public native long getLong(long address); // 設置或獲取指定內存的byte值 // 相當于獲取address后面一個字節的數據,轉化成十進制返回給你 public native byte getByte(long address); // 直接在內存級別給adderess地址后面的1個字節設置 public native void putByte(long address, byte x);老王:這里提供一些操作系統級別的直接申請內存、釋放內存的方式。
同時不受java語法的限制,提供內存級別的直接獲取數據,修改數據的方式;unsafe操作內存這塊內存我們目前只需要了解即可,對我們后續并發的學習影響不大。
老王:這里說可以直接分配內存,釋放內存,直接從內存級別修改數據的操作理解了沒?
小陳:這里說的通過操作系統級別的分配內存,釋放內存,其實就是調用操作系統底層的api去申請和釋放內存吧。
老王:是的,unsafe確實是通過調用操作提供的能力直接去申請和釋放內存的。
小陳:上面說的不受java語法限制,直接修改內存數據是咋回事?
老王:比如說某個對象的prinvate int value屬性,這里的value屬性是private的,如果這個對象沒有提供對應的訪問方法,在對象外部是訪問不到這個value屬性的對不對?
小陳:嗯嗯,是的,正常來說只有在對象的內部可以訪問這個private屬性,外部是訪問不到的。
老王:然而這個對象的數據本質上還是保存在內存里面的,而unsafe提供了直接對某個內存地址讀取、修改的操作,這樣就可以突破java語法的限制了。
小陳:哦哦,原來是這樣啊,相當于就是直接通過內存地址address,找到這塊內存然后直接操作這個內存塊的數據了。
老王:嗯嗯,沒錯;就是直接操作內存塊的數據,接下來我們再講講unsafe提供的cas操作。
unsafe提供的CAS操作
老王:這塊內容比較重要,JUC提供的很多Atomic原子類、基于AQS實現的并發工具,底層都是通過CAS操作去實現的。下面我們就說說unsafe提供的cas操作:
老王:假如目前有一個Test類是這樣子的:
public class Test {private DemoClass demo;private int intValue;private long longValue; }有一個Test類的對象 Test o = new Test();
這個時候想要突破java語法的限制,直接修改對象o的private修飾的demo屬性。可以通過CAS操作直接去修改對象o里面的demo屬性,使用unsafe提供的下面方法:
(1)o就是你要操作的對象
(2)offset就是demo屬性在對象o內部的位置,或者偏移量
(3)expected就是demo期待的值
(4)x就是你希望設置的新值,只有demo的值 == expect的時候,才能將demo的值設置成x
public final native boolean compareAndSwapObject(Object o,long offset,Object expected,Object x);執行CAS操作大致是這樣的,根據 對象o的地址,demo屬性相對于o的偏移量offset,直接計算得到demo所在內存的位置,然后直接將demo的值從內存取出進行CAS(比較替換操作):
同理對于,執行CAS操作替換Test類對象o內部的int值和long值,unsafe提供了如下兩個方法:
public final native boolean compareAndSwapInt(Object o,long offset,int expected,int x);public final native boolean compareAndSwapLong(Object o,long offset,long expected,long x);底層執行CAS替換的原理跟上面畫圖將的demo其實是一樣的,這里就不再贅述了。
unsafe將線程掛起和恢復
unsafe類提供類將一個線程掛起、講一個掛起的線程喚醒的方法,分別是park和unpark,我們看如下的代碼:
park方法
//線程調用該方法,線程將一直阻塞直到被喚醒,或者超時,或者中斷條件出現。 public native void park(boolean isAbsolute, long time);(1) isAbsolute是否是絕對時間,當isAbsolute == true ,后面time的時間單位是ms;當為false的時候,后面time參數的時間單位是ns。
(2)time > 0時候,表示大概要將線程掛起time的時間,過了時間后自動將線程喚醒。當time = 0的時候,表示一直將線程掛起,直到有人調用unpark方法將線程喚醒。
unpark方法
public native void unpark(Object thread);直接將正在被掛起的thread線程喚醒,讓它繼續干活
LockSupport
LockSupport是對unsafe中park和unpark功能封裝的一個工具類,提供了阻塞和喚醒功能。
我們可以直接使用LockSupport的方法達到掛起和恢復線程的效果,LockSupport方法的源碼如下:
public class LockSupport {public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);// 這里直接調用unsafe的park方法將線程掛起UNSAFE.park(false, 0L);setBlocker(t, null);}public static void unpark(Thread thread) {if (thread != null)// 直接調用unsafe的unpark方法將線程喚醒UNSAFE.unpark(thread);} }老王:由于我們自己編寫的java程序不能直接使用unsafe工具類,所以啊JDK還是有一些工具類對unsafe類的功能進行封裝,然后我們就直接使用這些封裝的工具類即可。
內存屏障
unsafe提供了幾種內存屏障:
// 在該方法之前的所有讀操作,一定在load屏障之前執行完成 public native void loadFence(); // 在該方法之前的所有寫操作,一定在store屏障之前執行完成 public native void storeFence(); // 在該方法之前的所有讀寫操作,一定在full屏障之前執行完成,這個內存屏障相當于上面兩個的合體功能 public native void fullFence();老王:小陳啊,關于上述講解的unsafe提供的幾類操作系統級別的功能,理解清楚了嗎?
小陳:恩恩,大致上清楚了,我看比較重要的還是:內存級別操作數據,cas操作,線程掛起park和喚醒unpark。
老王:那我們本章就先到這里了,我們下一章節再聊。
小陳:我們下一章見。
關注小陳,公眾號上更多更全的文章
JAVA并發文章目錄(公眾號)
JAVA并發專題 《筑基篇》
1.什么是CPU多級緩存模型?
2.什么是JAVA內存模型?
3.線程安全之可見性、有序性、原子性是什么?
4.什么是MESI緩存一致性協議?怎么解決并發的可見性問題?
JAVA并發專題《練氣篇》
5.volatile怎么保證可見性?
6.什么是內存屏障?具有什么作用?
7.volatile怎么通過內存屏障保證可見性和有序性?
8.volatile為啥不能保證原子性?
9.synchronized是個啥東西?應該怎么使用?
10.synchronized底層之monitor、對象頭、Mark Word?
11.synchronized底層是怎么通過monitor進行加鎖的?
12.synchronized的鎖重入、鎖消除、鎖升級原理?無鎖、偏向鎖、輕量級鎖、自旋、重量級鎖
13.synchronized怎么保證可見性、有序性、原子性?
JAVA并發專題《結丹篇》
14. JDK底層Unsafe類是個啥東西?
15.unsafe類的CAS是怎么保證原子性的?
16.Atomic原子類體系講解
17.AtomicInteger、AtomicBoolean的底層原理
18.AtomicReference、AtomicStampReference底層原理
19.Atomic中的LongAdder底層原理之分段鎖機制
20.Atmoic系列Strimped64分段鎖底層實現源碼剖析
JAVA并發專題《金丹篇》
21.AQS是個啥?為啥說它是JAVA并發工具基礎框架?
22.基于AQS的互斥鎖底層源碼深度剖析
23.基于AQS的共享鎖底層源碼深度剖析
24.ReentrantLock是怎么基于AQS實現獨占鎖的?
25.ReentrantLock的Condition機制底層源碼剖析
26.CountDownLatch 門栓底層源碼和實現機制深度剖析
27.CyclicBarrier 柵欄底層源碼和實現機制深度剖析
28.Semaphore 信號量底層源碼和實現機深度剖析
29.ReentrantReadWriteLock 讀寫鎖怎么表示?
30. ReentrantReadWriteLock 讀寫鎖底層源碼和機制深度剖析
JAVA并發專題《元神篇》并發數據結構篇
31.CopyOnAarrayList 底層分析,怎么通過寫時復制副本,提升并發性能?
32.ConcurrentLinkedQueue 底層分析,CAS 無鎖化操作提升并發性能?
33.ConcurrentHashMap詳解,底層怎么通過分段鎖提升并發性能?
34.LinkedBlockedQueue 阻塞隊列怎么通過ReentrantLock和Condition實現?
35.ArrayBlockedQueued 阻塞隊列實現思路竟然和LinkedBlockedQueue一樣?
36.DelayQueue 底層源碼剖析,延時隊列怎么實現?
37.SynchronousQueue底層原理解析
JAVA并發專題《飛升篇》線程池底層深度剖析
38. 什么是線程池?看看JDK提供了哪些默認的線程池?底層竟然都是基于ThreadPoolExecutor的?
39.ThreadPoolExecutor 構造函數有哪些參數?這些參數分別表示什么意思?
40.內部有哪些變量,怎么表示線程池狀態和線程數,看看道格.李大神是怎么設計的?
41. ThreadPoolExecutor execute執行流程?怎么進行任務提交的?addWorker方法干了啥?什么是workder?
42. ThreadPoolExecutor execute執行流程?何時將任務提交到阻塞隊列? 阻塞隊列滿會發生什么?
43. ThreadPoolExecutor 中的Worker是如何執行提交到線程池的任務的?多余Worker怎么在超出空閑時間后被干掉的?
44. ThreadPoolExecutor shutdown、shutdownNow內部核心流程
45. 再回頭看看為啥不推薦Executors提供幾種線程池?
46. ThreadPoolExecutor線程池篇總結
總結
以上是生活随笔為你收集整理的14.JDK底层Unsafe类是个啥东西?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库无法连接的几种情况
- 下一篇: 生态“群海”:数字化转型的供需之变