LockSupport实现原理

前文中了解到AQS借助LockSupport.park和LockSupport.unpark完成线程的阻塞和唤醒,那么LockSupport内部又是怎么实现的?这是一个什么类?

LockSupport是用于使用锁阻塞线程的基础实现,是其他同步类的基础,这个类为每个使用它的线程关联一个许可证(有点类似于Semaphore),如果许可证可用,线程调用park方法时会立即返回,线程正常执行,否则当前线程阻塞,直到有其他线程调用unpark使得许可证可用,此时线程被唤醒,再次尝试获取许可证,其内部定义的park和unpark方法提供了阻塞和解决阻塞的基本实现。

LockSupport常见函数如下表所示:

函数名称 说明 备注
void park() 阻塞当前线程 在下列情况发生时唤醒:
1.调用unpark函数,释放该线程的许可;
2.该线程被中断;
3.设置的阻塞超时时间耗尽;
4.到达设置的指定时间
void park(Object blocker) 使用指定的blocker对象阻塞当前线程 唤醒条件,park中已说明
void parkNanos(long nanos) 阻塞当前线程直到超时时间耗尽,nanos为指定的超时时间 唤醒条件,park中已说明
void parkNanos(Object blocker, long nanos) 在超时时间耗尽前,使用指定的blocker对象阻塞当前线程,如果在到达超时时间后,许可仍不可用,则结束阻塞 唤醒条件,park中已说明
void parkUntil(long deadline) 在指定时间前,阻塞该线程,如果在到达指定时间后,许可仍不可用,则结束阻塞 唤醒条件,park中已说明
void parkUntil(Object blocker, long deadline) 在指定时间前,使用指定的对象阻塞该线程,如果在到达指定时间后,许可仍不可用,则结束阻塞 唤醒条件,park中已说明
void unpark(Thread thread) 用于唤醒传入的在阻塞中的线程 /
Object getBlocker(Thread t) 获取当前线程的阻塞对象 /
void setBlocker(Thread t, Object arg) 使用指定对象为线程设置阻塞对象 /

LockSupport.park

ReentrantLock中调用LockSupport.park代码如下所示:

1// AbstractQueuedSynchronizer.Java
2private final boolean parkAndCheckInterrupt() {
3    LockSupport.park(this);
4    return Thread.interrupted();
5}

在LockSupport中,void park(Object blocker)实现代码如下:

1// LockSupport.java
2public static void park(Object blocker) {
3    Thread t = Thread.currentThread();
4    setBlocker(t, blocker);
5    UNSAFE.park(false0L);
6    setBlocker(t, null);
7}

可以看到在park流程中主要包含以下过程:

  1. 获取当前线程

  2. 将传入的对象设置为该线程的parkBlocker

    setBlocker函数实现如下所示:

    1private static void setBlocker(Thread t, Object arg) {
    2   // Even though volatile, hotspot doesn't need a write barrier here.
    3   UNSAFE.putObject(t, parkBlockerOffset, arg);
    4}

    可以看到这里新出现了UNSAFE和parkBlockerOffset两个标识,这两个是用来干嘛的?我们一起看看其声明的代码:

    1private static final sun.misc.Unsafe UNSAFE;
    2private static final long parkBlockerOffset;
    1static {
    2   try {
    3       UNSAFE = sun.misc.Unsafe.getUnsafe();
    4       Class<?> tk = Thread.class;
    5       parkBlockerOffset = UNSAFE.objectFieldOffset
    6           (tk.getDeclaredField("parkBlocker"));
    7       .....
    8   } catch (Exception ex) { throw new Error(ex); }
    9}

    可以看到UNSAFE对象是通过Unsafe.getUnsafe()获取的,那么Unsafe这个类到底是干嘛的?

    大家都知道Java对象在内存中创建,大多数情况下我们都是通过类的对象去修改和访问内存中的数据的,那么如果需要直接从内存修改某一对象的取值,应该怎么做呢?就是使用Unsafe类,该类只允许在JDK信任的类中调用(当前也可以用反射实例化该类对象)。

    在Unsafe类中定义了两个重要函数park和unpark,其中park用于实现线程阻塞,unpark用于实现线程唤醒(Unsafe本质上是操作线程的Parker对象来完成线程阻塞和唤醒的,具体见参考链接,了解即可),上文中的parkBlockerOffset正是定义了Thread类的parkBlocker属性成员的内存偏移量,使用该值再结合Unsafe对象就可以实现直接操作内存中的parkBlocker值的目的,Thread类中的parkBlocker声明如下:

    1// Thread.java
    2volatile Object parkBlocker;

    可以得到这一环节主要是将AQS作为blocker设置到当前线程的parkBlocker成员属性上。

CAS底层也是通过Unsafe执行的

  1. 执行UNSAFE.park

    结合上文可知,这步完成后,当前线程阻塞

  2. 设置线程的parkBlocker为null

    第三步中线程处于阻塞状态,当然就不能执行设置parkBlocker为null的操作了,那么什么时候执行呢?当线程从阻塞状态唤醒时,执行该步骤,使得线程的parkBlocker对象恢复初始状态。

LockSupport.unpark

LockSupport.unpark代码如下所示:

1// LockSupport.java
2public static void unpark(Thread thread) {
3    if (thread != null)
4        UNSAFE.unpark(thread);
5}

可以看出当传入的线程不为空时,执行Unsafe的park函数唤醒当前线程,取消阻塞,此时继续执行park函数中的setBlocker(null),将parkBlocker成员设置为null。

参考链接

https://blog.csdn.net/a7980718/article/details/83661613


原文始发于微信公众号(小海编码日记):LockSupport实现原理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/67722.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!