一、前言
最開始打算分析ReentrantLock,但是分析到最後,發現離不開LockSuport的支持,所以,索性就先開始分析LockSupport,因為它是鎖中的基礎,是一個提供鎖機制的工具類,所以先對其進行分析。
二、LockSupport源碼分析
2.1 類的屬性
public class LockSupport { // Hotspot implementation via intrinsics API private static final sun.misc.Unsafe UNSAFE; // 表示內存偏移地址 private static final long parkBlockerOffset; // 表示內存偏移地址 private static final long SEED; // 表示內存偏移地址 private static final long PROBE; // 表示內存偏移地址 private static final long SECONDARY; static { try { // 獲取Unsafe實例 UNSAFE = sun.misc.Unsafe.getUnsafe(); // 線程類類型 Class<?> tk = Thread.class; // 獲取Thread的parkBlocker字段的內存偏移地址 parkBlockerOffset = UNSAFE.objectFieldOffset (tk.getDeclaredField("parkBlocker")); // 獲取Thread的threadLocalRandomSeed字段的內存偏移地址 SEED = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSeed")); // 獲取Thread的threadLocalRandomProbe字段的內存偏移地址 PROBE = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomProbe")); // 獲取Thread的threadLocalRandomSecondarySeed字段的內存偏移地址 SECONDARY = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSecondarySeed")); } catch (Exception ex) { throw new Error(ex); } } }View Code
說明:UNSAFE字段表示sun.misc.Unsafe類,查看其源碼,點擊在這裡,一般程序中不允許直接調用,而long型的表示實例對象相應字段在內存中的偏移地址,可以通過該偏移地址獲取或者設置該字段的值。
2.2 類的構造函數
// 私有構造函數,無法被實例化 private LockSupport() {}
說明:LockSupport只有一個私有構造函數,無法被實例化。
2.3 核心函數分析
在分析LockSupport函數之前,先引入sun.misc.Unsafe類中的park和unpark函數,因為LockSupport的核心函數都是基於Unsafe類中定義的park和unpark函數,下面給出兩個函數的定義。
public native void park(boolean isAbsolute, long time); public native void unpark(Thread thread);
說明:對兩個函數的說明如下
① park函數,阻塞線程,並且該線程在下列情況發生之前都會被阻塞:① 調用unpark函數,釋放該線程的許可。② 該線程被中斷。③ 設置的時間到了。並且,當time為絕對時間時,isAbsolute為true,否則,isAbsolute為false。當time為0時,表示無限等待,直到unpark發生。
② unpark函數,釋放線程的許可,即激活調用park後阻塞的線程。這個函數不是安全的,調用這個函數時要確保線程依舊存活。
1. park函數
park函數有兩個重載版本,方法摘要如下
public static void park(); public static void park(Object blocker);
說明:兩個函數的區別在於park()函數沒有沒有blocker,即沒有設置線程的parkBlocker字段。park(Object)型函數如下。
public static void park(Object blocker) { // 獲取當前線程 Thread t = Thread.currentThread(); // 設置Blocker setBlocker(t, blocker); // 獲取許可 UNSAFE.park(false, 0L); // 重新可運行後再此設置Blocker setBlocker(t, null); }View Code
說明:調用park函數時,首先獲取當前線程,然後設置當前線程的parkBlocker字段,即調用setBlocker函數,之後調用Unsafe類的park函數,之後再調用setBlocker函數。那麼問題來了,為什麼要在此park函數中要調用兩次setBlocker函數呢?原因其實很簡單,調用park函數時,當前線程首先設置好parkBlocker字段,然後再調用Unsafe的park函數,此後,當前線程就已經阻塞了,等待該線程的unpark函數被調用,所以後面的一個setBlocker函數無法運行,unpark函數被調用,該線程獲得許可後,就可以繼續運行了,也就運行第二個setBlocker,把該線程的parkBlocker字段設置為null,這樣就完成了整個park函數的邏輯。如果沒有第二個setBlocker,那麼之後沒有調用park(Object blocker),而直接調用getBlocker函數,得到的還是前一個park(Object blocker)設置的blocker,顯然是不符合邏輯的。總之,必須要保證在park(Object blocker)整個函數執行完後,該線程的parkBlocker字段又恢復為null。所以,park(Object)型函數裡必須要調用setBlocker函數兩次。setBlocker方法如下。
private static void setBlocker(Thread t, Object arg) { // 設置線程t的parkBlocker字段的值為arg UNSAFE.putObject(t, parkBlockerOffset, arg); }View Code
說明:此方法用於設置線程t的parkBlocker字段的值為arg。
另外一個無參重載版本,park()函數如下。
public static void park() { // 獲取許可,設置時間為無限長,直到可以獲取許可 UNSAFE.park(false, 0L); }View Code
說明:調用了park函數後,會禁用當前線程,除非許可可用。在以下三種情況之一發生之前,當前線程都將處於休眠狀態,即下列情況發生時,當前線程會獲取許可,可以繼續運行。
① 其他某個線程將當前線程作為目標調用 unpark。
② 其他某個線程中斷當前線程。
③ 該調用不合邏輯地(即毫無理由地)返回。
2. parkNanos函數
此函數表示在許可可用前禁用當前線程,並最多等待指定的等待時間。具體函數如下。
public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { // 時間大於0 // 獲取當前線程 Thread t = Thread.currentThread(); // 設置Blocker setBlocker(t, blocker); // 獲取許可,並設置了時間 UNSAFE.park(false, nanos); // 設置許可 setBlocker(t, null); } }View Code
說明:該函數也是調用了兩次setBlocker函數,nanos參數表示相對時間,表示等待多長時間。
3. parkUntil函數
此函數表示在指定的時限前禁用當前線程,除非許可可用。具體函數如下。
public static void parkUntil(Object blocker, long deadline) { // 獲取當前線程 Thread t = Thread.currentThread(); // 設置Blocker setBlocker(t, blocker); UNSAFE.park(true, deadline); // 設置Blocker為null setBlocker(t, null); }View Code
說明:該函數也調用了兩次setBlocker函數,deadline參數表示絕對時間,表示指定的時間。
4. unpark函數
此函數表示如果給定線程的許可尚不可用,則使其可用。如果線程在 park 上受阻塞,則它將解除其阻塞狀態。否則,保證下一次調用 park 不會受阻塞。如果給定線程尚未啟動,則無法保證此操作有任何效果。具體函數如下。
public static void unpark(Thread thread) { if (thread != null) // 線程為不空 UNSAFE.unpark(thread); // 釋放該線程許可 }View Code
說明:釋放許可,指定線程可以繼續運行。
三、示例說明
3.1 實現兩線程同步
1. 使用wait/notify實現
package com.hust.grid.leesf.locksupport; class MyThread extends Thread { public void run() { synchronized (this) { System.out.println("before notify"); notify(); System.out.println("after notify"); } } } public class WaitAndNotifyDemo { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); synchronized (myThread) { try { myThread.start(); // 主線程睡眠3s Thread.sleep(3000); System.out.println("before wait"); // 阻塞主線程 myThread.wait(); System.out.println("after wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } }View Code
運行結果
before wait before notify after notify after wait
說明:具體的流程圖如下
使用wait/notify實現同步時,必須先調用wait,後調用notify,如果先調用notify,再調用wait,將起不了作用。具體代碼如下
package com.hust.grid.leesf.locksupport; class MyThread extends Thread { public void run() { synchronized (this) { System.out.println("before notify"); notify(); System.out.println("after notify"); } } } public class WaitAndNotifyDemo { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); myThread.start(); // 主線程睡眠3s Thread.sleep(3000); synchronized (myThread) { try { System.out.println("before wait"); // 阻塞主線程 myThread.wait(); System.out.println("after wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } }View Code
運行結果:
before notify after notify before wait
說明:由於先調用了notify,再調用的wait,此時主線程還是會一直阻塞。
3.2 使用park/unpark實現
package com.hust.grid.leesf.locksupport; import java.util.concurrent.locks.LockSupport; class MyThread extends Thread { private Object object; public MyThread(Object object) { this.object = object; } public void run() { System.out.println("before unpark"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取blocker System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object)); // 釋放許可 LockSupport.unpark((Thread) object); // 再次獲取blocker System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object)); System.out.println("after unpark"); } } public class ParkAndUnparkDemo { public static void main(String[] args) { MyThread myThread = new MyThread(Thread.currentThread()); myThread.start(); System.out.println("before park"); // 獲取許可 LockSupport.park("ParkAndUnparkDemo"); System.out.println("after park"); } }View Code
運行結果:
before park before unpark Blocker info ParkAndUnparkDemo after park Blocker info null after unpark
說明:本程序先執行park,然後在執行unpark,進行同步,並且在unpark的前後都調用了getBlocker,可以看到兩次的結果不一樣,並且第二次調用的結果為null,這是因為在調用unpark之後,執行了Lock.park(Object blocker)函數中的setBlocker(t, null)函數,所以第二次調用getBlocker時為null。
上例是先調用park,然後調用unpark,現在修改程序,先調用unpark,然後調用park,看能不能正確同步。具體代碼如下
package com.hust.grid.leesf.locksupport; import java.util.concurrent.locks.LockSupport; class MyThread extends Thread { private Object object; public MyThread(Object object) { this.object = object; } public void run() { System.out.println("before unpark"); // 釋放許可 LockSupport.unpark((Thread) object); System.out.println("after unpark"); } } public class ParkAndUnparkDemo { public static void main(String[] args) { MyThread myThread = new MyThread(Thread.currentThread()); myThread.start(); try { // 主線程睡眠3s Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("before park"); // 獲取許可 LockSupport.park("ParkAndUnparkDemo"); System.out.println("after park"); } }View Code
運行結果:
before unpark after unpark before park after park
說明:可以看到,在先調用unpark,再調用park時,仍能夠正確實現同步,不會造成由wait/notify調用順序不當所引起的阻塞。因此park/unpark相比wait/notify更加的靈活。
2. 中斷響應
看下面示例
package com.hust.grid.leesf.locksupport; import java.util.concurrent.locks.LockSupport; class MyThread extends Thread { private Object object; public MyThread(Object object) { this.object = object; } public void run() { System.out.println("before interrupt"); try { // 休眠3s Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Thread thread = (Thread) object; // 中斷線程 thread.interrupt(); System.out.println("after interrupt"); } } public class InterruptDemo { public static void main(String[] args) { MyThread myThread = new MyThread(Thread.currentThread()); myThread.start(); System.out.println("before park"); // 獲取許可 LockSupport.park("ParkAndUnparkDemo"); System.out.println("after park"); } }View Code
運行結果:
before park
before interrupt
after interrupt
after park
說明:可以看到,在主線程調用park阻塞後,在myThread線程中發出了中斷信號,此時主線程會繼續運行,也就是說明此時interrupt起到的作用與unpark一樣。
四、總結
LockSupport用來創建鎖和其他同步類的基本線程阻塞原語。簡而言之,當調用LockSupport.park時,表示當前線程將會等待,直至獲得許可,當調用LockSupport.unpark時,必須把等待獲得許可的線程作為參數進行傳遞,好讓此線程繼續運行。
經過研究LockSupport源碼,對LockSupport的工作機制有了詳細的了解,閱讀源碼受益匪淺,謝謝各位園友觀看~