LockSupport源码分析

Posted jxkun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LockSupport源码分析相关的知识,希望对你有一定的参考价值。

目录

LockSupport源码分析

LockSupport是Java6引入的一个工具类, 用于挂起和唤醒线程;

LockSupport 通过提供park() 和 unpark() 方法实现阻塞线程和解除线程阻塞, 实现阻塞与解除阻塞是基于许可(permit), permit相当于一个信号量,只能取0和1, 默认为0;

  • park(): 若permit为1, 则permit减1为0; 若permit为0, 则阻塞当前线程;
  • unpark(Thread): 若permit为0, permit加1; 若permit为1, permit不变; 但效果都是唤醒指定线程;

由于permit默认为0, 因此一般是先调用park()之后, 当前线程进入阻塞, 然后等待其他线程调用unpark(Thread)唤醒, 或者遇到中断会被唤醒;

LockSupport的实现

LockSupport 是基于 sun.misc.Unsafe 实现的;

1. 内部重要的属性:

  • UNSAFE: Unsafe对象, 提供CAS操作, 获取对象内存中字段的偏移量, 设置对象偏移量对应的字段的值;
  • parkBlockerOffset 存储Thread类对象中parkBlocker字段的偏移量;

    Thread类中, parkBlocker用于存储引起线程阻塞的对象(即: 线程所等待的资源); parkBlocker字段是特地为park()设计的, 在park()调用后, 通过调用setBlocker(Thread t, Object arg)设置线程所等待的资源, 此时用jstack工具进行堆栈分析时, 可以看到该阻塞的线程正在等待的资源arg;
    技术分享图片

private static final sun.misc.Unsafe UNSAFE; 
private static final long parkBlockerOffset;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        parkBlockerOffset = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("parkBlocker"));
        ...
    } catch (Exception ex) { throw new Error(ex); }
}

2. getBlocker(Thread) 与 setBlocker(Thread t, Object arg)源码

getBlocker 与 setBlocker 使用Unsafe类通过内存偏移量设置的原因:

因为要设置的parkBlocker的线程已经被阻塞了, 通过调用Thread对象的set方法由于阻塞无法被执行; 然而使用Unsafe类通过内存偏移地址设置并不受Thread对象被阻塞的影响;

/**
 * 唤醒线程 t
 * @param  t 想要唤醒的线程
 * @return  返回parkBlocker对象
 */
public static Object getBlocker(Thread t) {
    if (t == null)
        throw new NullPointerException();
    return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
/**
 * 设置 parkBlocker的值
 * @param t   被阻塞的线程
 * @param 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);
}

3. park的其他几个方法

  • park(Object blocker)
  • parkNanos(long nanos)
  • parkNanos(Object blocker, long nanos)
  • parkUntil(long deadline)
  • parkUntil(Object blocker, long deadline)

内部实现源码:

public static void park(Object blocker) {
    Thread t = Thread.currentThread(); // 获取当前线程 
    setBlocker(t, blocker); // 设置引起当前线程阻塞的资源
    UNSAFE.park(false, 0L); // 将当前线程阻塞
    setBlocker(t, null); // 该线程被唤醒后, 将blocker置为null
}
// 阻塞当前线程, 最长不超过nanos纳秒
public static void parkNanos(long nanos) {
    if (nanos > 0)
        UNSAFE.park(false, nanos); // 超时自动唤醒
}
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);
    }
}
// 阻塞当前线程, 知道deadline时间(从1970年开始到deadline时间的毫秒数)
public static void parkUntil(long deadline) {
    UNSAFE.park(true, deadline);
}
public static void parkUntil(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    setBlocker(t, null);
}

4. park()/unpark() 与 wait()/notify()区别:

  • park(), wait() 都是阻塞当前线程, 但是wait()需要获取synchronize监视器才能调用;
  • park() 可以设置getBlocker, 使用jstack堆栈分析时更加方便;
  • unpark() 可以唤醒指定线程, 而notify()唤醒一个随机的在该监视器上等待的线程, notifyAll()唤醒所有在该监视器上等待的线程;

以上是关于LockSupport源码分析的主要内容,如果未能解决你的问题,请参考以下文章

JUCJDK1.8源码分析之LockSupport

提升--08---LockSupport淘宝面试题与源码阅读方法论

#yyds干货盘点# JUC锁: LockSupport详解

java并发编程之LockSupport

JUC之LockSupport构建同步组件的基本工具

LockSupport使用及源码详解