| 轉載:http://my.oschina.net/readjava/blog/282882 要學習JAVA中是如何實現線程間的鎖,就得從LockSupport這個類先說起,因為這個類實現了底層的一些方法,各種的鎖實現都是這個基礎上發展而來的。這個類方法很少,但理解起來需要花費一點時間,因為涉及了很多底層的知識,這些都是我們平時不關心的。 上源代碼: |
package java.util.concurrent.locks; import java.util.concurrent.*; import sun.misc.Unsafe; public class LockSupport { private LockSupport() {} // Cannot be instantiated. // Hotspot implementation via intrinsics API private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long parkBlockerOffset; static { try { parkBlockerOffset = unsafe.objectFieldOffset (java.lang.Thread.class.getDeclaredField("parkBlocker")); } catch (Exception ex) { throw new Error(ex); } } private static void setBlocker(Thread t, Object arg) { // Even though volatile, hotspot doesn't need a write barrier here. unsafe.putObject(t, parkBlockerOffset, arg); } public static void unpark(Thread thread) { if (thread != null) unsafe.unpark(thread); } public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(false, 0L); setBlocker(t, null); } 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); } } public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(true, deadline); setBlocker(t, null); } public static Object getBlocker(Thread t) { return unsafe.getObjectVolatile(t, parkBlockerOffset); } public static void park() { unsafe.park(false, 0L); } public static void parkNanos(long nanos) { if (nanos > 0) unsafe.park(false, nanos); } public static void parkUntil(long deadline) { unsafe.park(true, deadline); } }
這個類提供的都是靜態方法,且無法被實例化。
在LockSupport中有兩個私有的成員變量:
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long parkBlockerOffset;
大家都知道JAVA語言是平台無關的,一次編譯,可以在任何平台上運行,但是如果真的不可以調用一些平台相關的方法嗎?其實unsafe類是可以做到的。
unsafe:是JDK內部用的工具類。它通過暴露一些Java意義上說“不安全”的功能給Java層代碼,來讓JDK能夠更多的使用Java代碼來實現一些原本是平台相關的、需要使用native語言(例如C或C++)才可以實現的功能。該類不應該在JDK核心類庫之外使用。
parkBlokcerOffset:parkBlocker的偏移量,從字面上理解是這么個東東。但是parkBlocker又是干嘛的?偏移量又是做什么的呢?讓我們來看看Thread類的實現:
//java.lang.Thread的源碼 /** * The argument supplied to the current call to * java.util.concurrent.locks.LockSupport.park. * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker * Accessed using java.util.concurrent.locks.LockSupport.getBlocker */ volatile Object parkBlocker;
問題1:parkBlocker又是干嘛的?
原來java.lang.Thread的實現當中有這么一個對象。從注釋上看,這個對象被LockSupport的setBlocker和getBlocker調用。查看JAVADOC會發現這么一段解釋:

大致意思是,這個對象是用來記錄線程被阻塞時被誰阻塞的。用於線程監控和分析工具來定位原因的。主要調用了LockSupport的getBlocker方法。
原來,parkBlocker是用於記錄線程是被誰阻塞的。可以通過LockSupport的getBlocker獲取到阻塞的對象。用於監控和分析線程用的。
問題2:偏移量又是做什么的?
static { try { parkBlockerOffset = unsafe.objectFieldOffset (java.lang.Thread.class.getDeclaredField("parkBlocker")); } catch (Exception ex) { throw new Error(ex); } }
這個要往后看一下,原來偏移量就算Thread這個類里面變量parkBlocker在內存中的偏移量。
JVM的實現可以自由選擇如何實現Java對象的“布局”,也就是在內存里Java對象的各個部分放在哪里,包括對象的實例字段和一些元數據之類。sun.misc.Unsafe里關於對象字段訪問的方法把對象布局抽象出來,它提供了objectFieldOffset()方法用於獲取某個字段相對Java對象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之類的方法可以使用前面獲取的偏移量來訪問某個Java對象的某個字段。
問題3:為什么要用偏移量來獲取對象?干嗎不要直接寫個get,set方法。多簡單?
仔細想想就能明白,這個parkBlocker就是在線程處於阻塞的情況下才會被賦值。線程都已經阻塞了,如果不通過這種內存的方法,而是直接調用線程內的方法,線程是不會回應調用的。
private static void setBlocker(Thread t, Object arg)
private static void setBlocker(Thread t, Object arg) { // Even though volatile, hotspot doesn't need a write barrier here. unsafe.putObject(t, parkBlockerOffset, arg); }
參數:
-
Thread t 需要被賦值Blocker的線程
-
Object arg 具體的Blocker對象
解讀:有了之前的理解,這個方法就很好理解了。對給定線程t的parkBlocker賦值。為了防止,這個parkBlocker被誤用,該方法是不對外公開的。
public static Object getBlocker(Thread t)
public static Object getBlocker(Thread t) { return unsafe.getObjectVolatile(t, parkBlockerOffset); }
參數:Thread t, 被操作的線程對象
返回:parkBlocker對象
解讀:從線程t中獲取他的parkerBlocker對象。這個方法是對外公開的。
是不是可以利用這個方法來寫一個監控程序,炫耀一把.
再講其他幾個方法之前,先談談park和unpark是做什么的.
/** * Unblock the given thread blocked on <tt>park</tt>, or, if it is * not blocked, cause the subsequent call to <tt>park</tt> not to * block. Note: this operation is "unsafe" solely because the * caller must somehow ensure that the thread has not been * destroyed. Nothing special is usually required to ensure this * when called from Java (in which there will ordinarily be a live * reference to the thread) but this is not nearly-automatically * so when calling from native code. * @param thread the thread to unpark. * */ public native void unpark(Object thread); /** * Block current thread, returning when a balancing * <tt>unpark</tt> occurs, or a balancing <tt>unpark</tt> has * already occurred, or the thread is interrupted, or, if not * absolute and time is not zero, the given time nanoseconds have * elapsed, or if absolute, the given deadline in milliseconds * since Epoch has passed, or spuriously (i.e., returning for no * "reason"). Note: This operation is in the Unsafe class only * because <tt>unpark</tt> is, so it would be strange to place it * elsewhere. */ public native void park(boolean isAbsolute, long time);
字面理解park,就算占住,停車的時候不就把這個車位給占住了么?起這個名字還是很形象的。unpark,占住的反義詞,就是釋放。把車從車位上開走。
翻譯一下:
-
park:阻塞當前線程,(1)當配對的unpark發生或者(2)配對的unpark已經發生或者線程被中斷時恢復(unpark先行,再執行park)。 (3)當absolute是false時,如果給定的時間是非0(負數)或者給定的時間(正數, 時間單位時毫秒)已經過去了(0的時候會一直阻塞着)。(4)當Absolute是true時,如果給定的時間(時間單位是納秒)過去了或者偽造的(在我理解是參數不合法時)線程會恢復中斷。這個操作是不安全的,所以在其他調用會很奇怪(奇怪?反正就是用的時候要小心)
-
unpark:當指定線程被park命令阻塞時unpark命令可以恢復阻塞。在park命令沒有被先調用過的時候,調用unpark,線程仍然不被阻塞。(翻譯的有點那個...).
理解一下,park與unpark命令是成對出現的。unpark必須要在park命令后執行。但是線程的恢復並不一定要用unpark, 因為park的時間參數,有些情況下線程會自己恢復。
public static void unpark(Thread thread)
public static void unpark(Thread thread) { if (thread != null) unsafe.unpark(thread); }
參數:Thread thread, 需要被中止掛起的線程
帶blocker參數的park方法
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(false, 0L); setBlocker(t, null); } 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); } } public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(true, deadline); setBlocker(t, null); }
參數:
-
Object blocker:用於記錄到線程中的parkBlocker對象。
-
nanos:在nanos時間后線程自動恢復掛起
-
deadline:在deadline時刻線程自動(這個毫秒其實就是自1970年1月1日0時起的毫秒數)
解讀:這三個方法其實是一個意思,把blocker放到線程當中,注意,這個park方法是一個阻塞的方法,除非4個條件
-
當配對的unpark發生或者
-
配對的unpark已經發生或者線程被中斷時恢復(unpark先行,再執行park)
-
當absolute是false時,如果給定的時間是非0(負數)或者給定的時間(正數, 時間單位時毫秒)已經過去了(0的時候會一直阻塞着)。
-
當Absolute是true時,如果給定的時間(時間單位是納秒)過去了或者偽造的(在我理解是參數不合法時)線程會恢復中斷。
不帶blocker參數的park方法
public static void park() { unsafe.park(false, 0L); } public static void parkNanos(long nanos) { if (nanos > 0) unsafe.park(false, nanos); } public static void parkUntil(long deadline) { unsafe.park(true, deadline); }
這三個方法跟上面一樣,唯一區別是沒有做parkBlocker的賦值操作。
來自我同事的並發編程網:
我們繼續看一下JVM是如何實現park方法的,park在不同的操作系統使用不同的方式實現,在linux下是使用的是系統方法pthread_cond_wait實現。實現代碼在JVM源碼路徑src/os/linux/vm/os_linux.cpp里的 os::PlatformEvent::park方法,代碼如下:
void os::PlatformEvent::park() {
int v ;
for (;;) {
v = _Event ;
if (Atomic::cmpxchg (v-1, &_Event, v) == v) break ;
}
guarantee (v >= 0, "invariant") ;
if (v == 0) {
// Do this the hard way by blocking ...
int status = pthread_mutex_lock(_mutex);
assert_status(status == 0, status, "mutex_lock");
guarantee (_nParked == 0, "invariant") ;
++ _nParked ;
while (_Event < 0) {
status = pthread_cond_wait(_cond, _mutex);
// for some reason, under 2.7 lwp_cond_wait() may return ETIME ...
// Treat this the same as if the wait was interrupted
if (status == ETIME) { status = EINTR; }
assert_status(status == 0 || status == EINTR, status, "cond_wait");
}
-- _nParked ;
// In theory we could move the ST of 0 into _Event past the unlock(),
// but then we'd need a MEMBAR after the ST.
_Event = 0 ;
status = pthread_mutex_unlock(_mutex);
assert_status(status == 0, status, "mutex_unlock");
}
guarantee (_Event >= 0, "invariant") ;
}
}
