Java LockSupport類(lèi)

1. 加鎖park與解鎖的 unpark

  • 以下三種情況會(huì)導(dǎo)致park返回亲雪。

If the permit is available then it is consumed and the call returns
immediately; otherwise
the current thread becomes disabled for thread scheduling
purposes and lies dormant until one of three things happens:

  • Some other thread invokes {@link #unpark unpark} with the
    current thread as the target; or
  • Some other thread {@linkplain Thread#interrupt interrupts}
    the current thread; or
  • The call spuriously (that is, for no reason) returns.

park 和 unpark 方法實(shí)際上是調(diào)用了 Unsafe 類(lèi)里的函數(shù).

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        U.park(false, 0L);
        setBlocker(t, null);
    }

    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        U.putObject(t, PARKBLOCKER, arg);
    }

    @HotSpotIntrinsicCandidate
    public native void putObject(Object o, long offset, Object x);

    @HotSpotIntrinsicCandidate
    public native void park(boolean isAbsolute, long time);
    public static void unpark(Thread thread) {
        if (thread != null)
            U.unpark(thread);
    }

  @HotSpotIntrinsicCandidate
    public native void unpark(Object thread);

2.hotspot源碼中的Parker

//在hotspot源碼的Thread.hpp中
  // JSR166 per-thread parker
 private:
  Parker*    _parker;
 public:
  Parker*     parker() { return _parker; } 

Parker 類(lèi)繼承自 PlatformParker茴迁,PlatformParker 的實(shí)現(xiàn)就是系統(tǒng)相關(guān)了崎脉,linux 的實(shí)現(xiàn)是利用了 POSIX 的 mutex 和 condition噪矛。

// 源碼位置hotspot/src/share/vm/rumtime/Park.hpp
class Parker : public os::PlatformParker {
public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  void park(bool isAbsolute, jlong time);
  void unpark();
};
//源碼位置hotspot/src/os/posix/vm/Os_posix.hpp
// JSR166 support
// PlatformParker provides the platform dependent base class for the
// Parker class. It basically provides the internal data structures:
// - mutex and convars
// which are then used directly by the Parker methods defined in the OS
// specific implementation files.
class PlatformParker : public CHeapObj<mtInternal> {
 protected:
  pthread_mutex_t _mutex[1];
  pthread_cond_t  _cond[2]; // one for relative times and one for absolute
};

Parker::park 方法中,主要是調(diào)用了 pthread_cond_wait 方法和 pthread_cond_timedwait趴梢。

void Parker::park(bool isAbsolute, jlong time) {

  // 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;

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;

  // Optional optimization -- avoid state transitions if there's
  // an interrupt pending.
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

  // Next, demultiplex/decode time arguments
  struct timespec absTime;
  if (time < 0 || (isAbsolute && time == 0)) { // don't wait at all
    return;
  }
  if (time > 0) {
    to_abstime(&absTime, time, isAbsolute);
  }

  // Enter safepoint region
  // Beware of deadlocks such as 6317397.
  // The per-thread Parker:: mutex is a classic leaf-lock.
  // In particular a thread must never block on the Threads_lock while
  // holding the Parker:: mutex.  If safepoints are pending both the
  // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
  ThreadBlockInVM tbivm(jt);

  // Don't wait if cannot get lock since interference arises from
  // unparking. Also re-check interrupt before trying wait.
  if (Thread::is_interrupted(thread, false) ||
      pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

  int status;
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert_status(status == 0, status, "invariant");
    // Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other and Java-level accesses.
    OrderAccess::fence();
    return;
  }

  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  assert(_cur_index == -1, "invariant");
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait(&_cond[_cur_index], _mutex);
    assert_status(status == 0, status, "cond_timedwait");
  }
  else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    status = pthread_cond_timedwait(&_cond[_cur_index], _mutex, &absTime);
    assert_status(status == 0 || status == ETIMEDOUT,
                  status, "cond_timedwait");
  }
  _cur_index = -1;

  _counter = 0;
  status = pthread_mutex_unlock(_mutex);
  assert_status(status == 0, status, "invariant");
  // Paranoia to ensure our locked and lock-free paths interact
  // correctly with each other and Java-level accesses.
  OrderAccess::fence();

  // If externally suspended while waiting, re-suspend
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}

Parker::unpark 設(shè)置 _counter 為1噩峦,再 unlock mutex,如果 _counter 之前小于 1笼痹,則調(diào)用 pthread_cond_signal 喚醒等待的線(xiàn)程配喳。

void Parker::unpark() {
  int status = pthread_mutex_lock(_mutex);
  assert_status(status == 0, status, "invariant");
  const int s = _counter;
  _counter = 1;
  // must capture correct index before unlocking
  int index = _cur_index;
  status = pthread_mutex_unlock(_mutex);
  assert_status(status == 0, status, "invariant");

  // Note that we signal() *after* dropping the lock for "immortal" Events.
  // This is safe and avoids a common class of futile wakeups.  In rare
  // circumstances this can cause a thread to return prematurely from
  // cond_{timed}wait() but the spurious wakeup is benign and the victim
  // will simply re-test the condition and re-park itself.
  // This provides particular benefit if the underlying platform does not
  // provide wait morphing.

  if (s < 1 && index != -1) {
    // thread is definitely parked
    status = pthread_cond_signal(&_cond[index]);
    assert_status(status == 0, status, "invariant");
  }
}


#endif 
  • _count相關(guān)
    HotSpot Parker用condition和mutex維護(hù)了一個(gè)_counter變量,park時(shí)凳干,變量_counter置為0晴裹,unpark時(shí),變量_counter置為1救赐。
    在 Parker::park 中有這么一段代碼涧团,如果 _counter 大于 0,則立即返回。在上面 unpark 的代碼中泌绣,我們看到 unpark 將 _counter 設(shè)置為 1喳瓣,也就是說(shuō):兩個(gè)線(xiàn)程之間的 park 和 unpark 不存在時(shí)序關(guān)系,可以先 unpark 再 park赞别,不會(huì)造成死鎖畏陕。這相對(duì)于存在依賴(lài)關(guān)系的 wait/notify 機(jī)制是一個(gè)巨大的優(yōu)點(diǎn)。
void Parker::park(bool isAbsolute, jlong time) {
  // 這中間省略了很多代碼
  int status ;
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    // Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other and Java-level accesses.
    OrderAccess::fence();
    return;
  }
  // 這后面省略了更多代碼

3.應(yīng)用

3.1AQS: AbstractQueuedSynchronizer

AbstractQueuedSynchronizer 中獲取鎖的代碼如下仿滔,這里使用 for 循環(huán)惠毁,而不是在 park 返回后就立即返回,也是為了排除中斷崎页、虛假喚醒等并非因資源可用而喚醒的情況鞠绰。

    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

3.2FutureTask

在 FutureTask 中,等待操作完成的 awaitDone 大致分為以下步驟:

  1. 先檢查是否存在中斷飒焦,是則拋異常 InterruptedException蜈膨;
  2. 是否已經(jīng)完成,是則返回牺荠;
  3. 進(jìn)入等待隊(duì)列中翁巍;
  4. 當(dāng)設(shè)置了超時(shí)時(shí)間 nanos 時(shí),調(diào)用 LockSupport.parkNanos 方法等待休雌;
  5. 沒(méi)有設(shè)置超時(shí)時(shí)間時(shí)灶壶,調(diào)用 LockSupport.park 方法等待。
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        // The code below is very delicate, to achieve these goals:
        // - call nanoTime exactly once for each call to park
        // - if nanos <= 0L, return promptly without allocation or nanoTime
        // - if nanos == Long.MIN_VALUE, don't underflow
        // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
        //   and we suffer a spurious wakeup, we will do no worse than
        //   to park-spin for a while
        long startTime = 0L;    // Special value 0L means not yet parked
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING)
                // We may have already promised (via isDone) that we are done
                // so never return empty-handed or throw InterruptedException
                Thread.yield();
            else if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            else if (q == null) {
                if (timed && nanos <= 0L)
                    return s;
                q = new WaitNode();
            }
            else if (!queued)
                queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
            else if (timed) {
                final long parkNanos;
                if (startTime == 0L) { // first time
                    startTime = System.nanoTime();
                    if (startTime == 0L)
                        startTime = 1L;
                    parkNanos = nanos;
                } else {
                    long elapsed = System.nanoTime() - startTime;
                    if (elapsed >= nanos) {
                        removeWaiter(q);
                        return state;
                    }
                    parkNanos = nanos - elapsed;
                }
                // nanoTime may be slow; recheck before parking
                if (state < COMPLETING)
                    LockSupport.parkNanos(this, parkNanos);
            }
            else
                LockSupport.park(this);
        }
    }

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杈曲,一起剝皮案震驚了整個(gè)濱河市驰凛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌担扑,老刑警劉巖恰响,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異涌献,居然都是意外死亡胚宦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)洁奈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)间唉,“玉大人绞灼,你說(shuō)我怎么就攤上這事利术。” “怎么了低矮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵印叁,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)轮蜕,這世上最難降的妖魔是什么昨悼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮跃洛,結(jié)果婚禮上率触,老公的妹妹穿的比我還像新娘。我一直安慰自己汇竭,他們只是感情好葱蝗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著细燎,像睡著了一般两曼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上玻驻,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天悼凑,我揣著相機(jī)與錄音,去河邊找鬼璧瞬。 笑死户辫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嗤锉。 我是一名探鬼主播寸莫,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼档冬!你這毒婦竟也來(lái)了膘茎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤酷誓,失蹤者是張志新(化名)和其女友劉穎披坏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盐数,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棒拂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玫氢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帚屉。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖漾峡,靈堂內(nèi)的尸體忽然破棺而出攻旦,到底是詐尸還是另有隱情,我是刑警寧澤生逸,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布牢屋,位于F島的核電站且预,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏烙无。R本人自食惡果不足惜锋谐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望截酷。 院中可真熱鬧涮拗,春花似錦、人聲如沸迂苛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灾部。三九已至康铭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赌髓,已是汗流浹背从藤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锁蠕,地道東北人夷野。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荣倾,于是被迫代替她去往敵國(guó)和親悯搔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容