LockSupport源碼詳細分析
一. LockSupport類介紹
前面分析中,阻塞和喚醒線程都會使用到LockSupport工具來完成相應工作,LockSupport定義了一組公共靜態方法,這些方法提供了最基本的線程阻塞和喚醒公共,而LockSupport也成為構建同步組件的基礎工具。
LockSupport定義了一組以park開頭的方法用來阻塞當前線程,以及upark方法用來喚醒線程。這些方法如下所示:
方法名稱 | 描述 |
---|---|
void park() | 阻塞當前線程,如果調用unpark(Thread)或者當前線程被中斷,才能從park()方法返回 |
void parkNanos(long nanos) | 阻塞當前線程,最長不超過nanos納秒,返回條件在park()的基礎上增加了超時返回 |
void parkUntil(long deadline) | 阻塞當前線程,直到deadline |
void unpark(Thread thread) | 喚醒處于阻塞的線程thread |
三種形式的 park 還各自支持一個 blocker 對象參數。此對象在線程受阻塞時被記錄,以允許監視工具和診斷工具確定線程受阻塞的原因。(這樣的工具可以使用方法 getBlocker(java.lang.Thread) 訪問 blocker。)建議最好使用這些形式,而不是不帶此參數的原始形式。在鎖實現中提供的作為 blocker 的普通參數是 this。
看下線程dump的結果來理解blocker的作用。
從線程dump結果可以看出:
有blocker的可以傳遞給開發人員更多的現場信息,通過jstack命令可以非常方便的監控具體的阻塞對象,方便定位問題。所以java6新增加帶blocker入參的系列park方法,替代原有的park方法。
Unsafe的park和unpark
LockSupport類是Java6(JSR166-JUC)引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了Unsafe類里的函數,歸結到Unsafe里,只有兩個函數:
/**
* 為指定線程提供“許可(permit)”
*/
public native void unpark(Thread jthread);
/**
* 阻塞指定時間等待“許可”。
* @param isAbsolute: 時間是絕對的,還是相對的
* @param time:等待許可的時間
*/
public native void park(boolean isAbsolute, long time);
上面的這個“許可”是不能疊加的,“許可”是一次性的。
比如線程B連續調用了三次unpark函數,當線程A調用park函數就使用掉這個“許可”,如果線程A再次調用park,則進入等待狀態。
注意,unpark函數可以先于park調用。比如線程B調用unpark函數,給線程A發了一個“許可”,那么當線程A調用park時,它發現已經有“許可”了,那么它會馬上再繼續運行。
可能有些朋友還是不理解“許可”這個概念,我們深入HotSpot的源碼來看看。
每個java線程都有一個Parker實例,Parker類是這樣定義的:
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
class PlatformParker : public CHeapObj<mtInternal> {
protected:
pthread_mutex_t _mutex [1] ;
pthread_cond_t _cond [1] ;
...
}
可以看到Parker類實際上用Posix的mutex,condition來實現的。在Parker類里的_counter字段,就是用來記錄所謂的“許可”的。
當調用park時,先嘗試直接能否直接拿到“許可”,即_counter>0
時,如果成功,則把_counter
設置為0,并返回:
void Parker::park(bool isAbsolute, jlong time) {
// Ideally we'd do something useful while spinning, such
// as calling unpackTime().
// Optional fast-path check:
// Return immediately if a permit is available.
// We depend on Atomic::xchg() having full barrier semantics
// since we are doing a lock-free update to _counter.
if (Atomic::xchg(0, &_counter) > 0) return;
如果不成功,則構造一個ThreadBlockInVM,然后檢查_counter
是不是>0,如果是,則把_counter
設置為0,unlock mutex并返回:
ThreadBlockInVM tbivm(jt);
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
否則,再判斷等待的時間,然后再調用pthread_cond_wait函數等待,如果等待返回,則把_counter設置為0,unlock mutex并返回:
if (time == 0) {
status = pthread_cond_wait (_cond, _mutex) ;
}
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
OrderAccess::fence();
當unpark時,則簡單多了,直接設置_counter
為1,再unlock mutext返回。如果_counter
之前的值是0,則還要調用pthread_cond_signal喚醒在park中等待的線程:
void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_counter = 1;
if (s < 1) {
if (WorkAroundNPTLTimedWaitHang) {
status = pthread_cond_signal (_cond) ;
assert (status == 0, "invariant") ;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
} else {
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
status = pthread_cond_signal (_cond) ;
assert (status == 0, "invariant") ;
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
簡而言之,是用mutex和condition保護了一個_counter的變量,當park時,這個變量置為了0,當unpark時,這個變量置為1。
值得注意的是在park函數里,調用pthread_cond_wait時,并沒有用while來判斷,所以posix condition里的"Spurious wakeup"一樣會傳遞到上層Java的代碼里。關于"Spurious wakeup",可以參考:并行編程之條件變量(posix condition variables)
3、LockSupport源碼分析
解釋完Unsafe的park和unpark的實現原理,我們再來看LockSupport的源碼時就會異常清晰,因為不復雜,所以直接看注釋吧。
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
private static void setBlocker(Thread t, Object arg) {
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
/**
* 返回提供給最近一次尚未解除阻塞的 park 方法調用的 blocker 對象。
* 如果該調用不受阻塞,則返回 null。
* 返回的值只是一個瞬間快照,即由于未解除阻塞或者在不同的 blocker 對象上受阻而具有的線程。
*/
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
/**
* 如果給定線程的許可尚不可用,則使其可用。
* 如果線程在 park 上受阻塞,則它將解除其阻塞狀態。
* 否則,保證下一次調用 park 不會受阻塞。
* 如果給定線程尚未啟動,則無法保證此操作有任何效果。
* @param thread: 要執行 unpark 操作的線程;該參數為 null 表示此操作沒有任何效果。
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/**
* 為了線程調度,在許可可用之前阻塞當前線程。
* 如果許可可用,則使用該許可,并且該調用立即返回;
* 否則,為線程調度禁用當前線程,并在發生以下三種情況之一以前,使其處于休眠狀態:
* 1. 其他某個線程將當前線程作為目標調用 unpark
* 2. 其他某個線程中斷當前線程
* 3. 該調用不合邏輯地(即毫無理由地)返回
*/
public static void park() {
UNSAFE.park(false, 0L);
}
/**
* 和park()方法類似,不過增加了等待的相對時間
*/
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
/**
* 和park()方法類似,不過增加了等待的絕對時間
*/
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
/**
* 和park()方法類似,只不過增加了暫停的同步對象
* @param blocker 導致此線程暫停的同步對象
* @since 1.6
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
/**
* parkNanos(long nanos)方法類似,只不過增加了暫停的同步對象
* @param blocker 導致此線程暫停的同步對象
* @since 1.6
*/
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
/**
* parkUntil(long deadline)方法類似,只不過增加了暫停的同步對象
* @param blocker 導致此線程暫停的同步對象
* @since 1.6
*/
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
r = 1; // avoid zero
UNSAFE.putInt(t, SECONDARY, r);
return r;
}
// 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 = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
4、例子
看完LockSupport的源碼,我們來動手寫幾個例子來驗證一下猜想是否正確。
4.1、先park再unpark
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
String a = new String("A");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("睡覺");
LockSupport.park(a);
System.out.println("起床");
}
});
t.setName("A-Name");
t.start();
Thread.sleep(300000);
System.out.println("媽媽喊我起床");
LockSupport.unpark(t);
}
}
輸出結果:
睡覺
媽媽喊我起床
起床
不過在等待的過程中,我們可以用jstack查看是否能夠打印出檢測的對象A,找到A-Name
這個線程確實看到了等待一個String對象
~ jps
5589 LockSupportTest
~ jstack 5589
"A-Name" #11 prio=5 os_prio=31 tid=0x00007fc143009800 nid=0xa803 waiting on condition [0x000070000c233000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076adf4d30> (a java.lang.String)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at com.github.locksupport.LockSupportTest$1.run(LockSupportTest.java:18)
at java.lang.Thread.run(Thread.java:745)
驗證完unpark,接著我們來驗證一下interrupt。
4.2、先interrupt再park
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
String a = new String("A");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("睡覺");
LockSupport.park(a);
System.out.println("起床");
System.out.println("是否中斷:" + Thread.currentThread().isInterrupted());
}
});
t.setName("A-Name");
t.start();
t.interrupt();
System.out.println("突然肚子很疼");
}
}
可以看到中斷后執行park會直接執行下面的方法,并不會拋出InterruptedException
,輸出結果如下:
突然肚子很疼
睡覺
起床
是否中斷:true
4.3、先unpark再park
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
String a = new String("A");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡覺");
LockSupport.park(a);
System.out.println("7點到,起床");
}
});
t.setName("A-Name");
t.start();
LockSupport.unpark(t);
System.out.println("提前上好鬧鐘7點起床");
}
}
按照上面說過的,先設置好許可(unpark)再獲取許可的時候不會進行等待,正如我們說的那樣輸出如下:
提前上好鬧鐘7點起床
睡覺
7點到,起床
線程一共有六種狀態,而park系列方法線程進入兩種狀態:WAITING等待狀態或TIMED_WAITING等待狀態。這兩種狀態都會使線程阻塞在當前位置。
那么怎么喚醒這兩種狀態的線程呢?
對于WAITING等待狀態有兩種喚醒方式:
- 調用對應的喚醒方法。這里就是LockSupport的unpark方法。
- 調用該線程變量的interrupt()方法,會喚醒該線程,并拋出InterruptedException異常。
對于TIMED_WAITING等待狀態來說,它比WAITING狀態多了一種喚醒方式,就是超過規定時間,那么線程會自動醒來。
思考一個問題
看完源碼后,是不是覺得LockSupport.park()和unpark()和object.wait()和notify()很相似,那么它們有什么區別呢?
- 面向的主體不一樣。LockSuport主要是針對Thread進進行阻塞處理,可以指定阻塞隊列的目標對象,每次可以指定具體的線程喚醒。Object.wait()是以對象為緯度,阻塞當前的線程和喚醒單個(隨機)或者所有線程。
- 實現機制不同。雖然LockSuport可以指定monitor的object對象,但和object.wait(),兩者的阻塞隊列并不交叉。可以看下測試例子。object.notifyAll()不能喚醒LockSupport的阻塞Thread.
對很少部分做整理,自己梳理一遍代碼思路更加清晰,膜拜原作者!侵刪!
本文參考:
智能推薦
HBase 0.92.1 Scan 源碼詳細分析
從這篇文章開始終于要討論比較正常版本的hbase了---0.92.1~~ Scan是hbase提供的非常重要的功能之一,我們的hbase分析系列就從這兒開始吧。 首先,我們有一些background的知識需要了解: 1.HBase是如何區分不同記錄的,大家可以參考http://punishzhou.iteye.com/blog/1266341,講的比較詳細 2.Region,MemStore,St...
Netty5.0的NioEventLoop源碼詳細分析
了解Netty線程模型的小伙伴應該都知道,Netty的線程有兩個NioEventLoopGroup線程池,一個是boss線程池,一個是worker線程池,其中worker線程池的任務如下: a.異步讀取通訊對端的消息,向ChannelPipeline發出讀事件 b.異步向通訊對端發送消息,調用ChannelPipeline發送消息接口 c.執行系統Task任務 d.執行定時任務 系統T...
[詳細分析]Java-ArrayList源碼全解析
目錄 類圖 屬性 構造方法 帶初始容量的構造方法 無參構造方法 帶一個集合參數的構造方法 插入方法 在列表最后添加指定元素 在指定位置添加指定元素 插入方法調用的其他私有方法 擴容方法 移除方法 移除指定下標元素方法 移除指定元素方法 私有移除方法 查找方法 查找指定元素的所在位置 查找指定位置的元素 序列化方法 反序列化方法 創建子數組 迭代器 創建迭代器方法 Itr屬性 Itr的hasNext...
JetPack之ViewModel最新源碼詳細分析
本文會基于最新版ViewModel使用方法與源碼進行詳細分析,從注冊到實現ViewModel界面數據如何保存與管理全部涉及。 ** 簡介: **ViewModel是JetPack系列庫之一,它用來對組件的界面數據進行管理,且當組件的狀態發生改變時數據依然留存。 優點:1.當所依賴組件的狀態發生改變時,例如屏幕旋轉等,界面數據不會發生改變 2.實現MVVM架構的基礎,在日常開發中,ViewModel...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...