通俗易懂的JUC源码剖析-LockSupport

前言

LockSupport是rt.jar下的工具类,它的作用是挂起和唤醒线程,它在JUC很多同步组件中都会用到,比如AQS。LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类方法的线程是没有许可证的。LockSupport内部是用Unsafe的park和unpark方法实现的。

主要方法

(1)void park()

调用park()方法会阻塞当前线程,除非当前线程已经拿到了与LockSupport关联的许可证。当其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时,或者调用当前线程的interrupt()方法时,当前线程会从阻塞状态返回。但是,需要注意的是,与调用sleep(),wait()等方法不同,调用park()方法被中断返回时,不会抛出InterruptedException异常。还有需要注意的是,park()方法返回时不会告诉你是什么原因返回(unpark或interrupt),但我们可以根据调用前后的中断标志是否改变来判断是否因为interrupt返回的。

(2)void unpark(Thread thread)

线程调用unpark方法时,如果参数线程thread没有持有许可证,则让其持有,如果thread在此前因调用park而被阻塞,则调用unpark后,该线程会被唤醒。如果thread之前没有调用park,则调用unpark后,再调用park,线程会立即返回,而不会阻塞挂起。用代码来说明:

import java.util.concurrent.locks.LockSupport;
public class UnparkDemo {
    public static void main(String[] args) {
        System.out.println("give current thread a permit");
        LockSupport.unpark(Thread.currentThread());
        System.out.println("current thread begin park");
        LockSupport.park();
        System.out.println("current thread end park");
    }
}

输出结果如下:

当前线程调用park方法后并没有阻塞挂起,而是立即返回了,因为此前调用unpark给了它许可证。

(3)void parkNanos(long nanos)

和park方法类似,但有个超时参数,如果等待了nanos(单位纳秒)时间后,当前线程没有被唤醒,会自动返回。

(4)void park(Object blocker)

当前线程调用park(blocker)被挂起时,blocker对象会被记录在当前线程内部,它的作用是当我们使用诊断工具(如jstack等)dump线程时,可以观察到线程被阻塞在哪个类上面。诊断工具是通过getBlocker()拿到blocker对象的。因此JDK推荐我们使用带有blocker参数的park方法,例如LockSupport.park(this)。同样用代码来说明下:

import java.util.concurrent.locks.LockSupport;
public class ParkWithBlockerDemo {
    public void parkWithBlocker() {
        LockSupport.park(this);
    }
    public static void main(String[] args) {
        ParkWithBlockerDemo demo = new ParkWithBlockerDemo();
        demo.parkWithBlocker();
    }
}

程序运行后,线程会被阻塞挂起,此时先用jps命令找到它的pid,然后用jstack pid执行,会有如下结果:

不加blocker参数的话,是不会有这个标记的,感兴趣的同学可以去试试。

park(blocker)方法源码:

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

代码逻辑很清晰明了,原理就是真正park之前,将blocker对象绑定在当前Thread内部的某个变量中,等park返回后,解除绑定。其中setBlocker源码如下:

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

其中parkBlockerOffset是Thread类parkBlocker变量的内存偏移量。

private static final long parkBlockerOffset;
parkBlockerOffset = UNSAFE.objectFieldOffset
    (tk.getDeclaredField("parkBlocker"));
(5)parkUntil(long deadline)

与parkNanos不同的是,deadline是个绝对时间戳,表示到某个时间点后,当前线程会被唤醒。

结束语

最后再来看个栗子来加深LockSupport的理解。

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedDeque<>();
    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        // 把当前线程放到队列末尾
        waiters.add(current);
        // 只有队列头部的线程才能获取锁
        while (waiters.peek() != current || locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            // 记录是否被中断过,并重置中断标志,这里其实就是忽略中断
            if (Thread.interrupted()) {
                wasInterrupted = true;
            }
        }
        // 移除队列头部元素,即成功获取锁的当前线程
        waiters.remove();
        // 重新响应之前的中断
        if (wasInterrupted) {
            current.interrupt();
        }
    }
    public void unlock() {
        locked.set(false);
        // 唤醒原本处于队列第二,现在处于头部的线程,让它尝试获取锁
        LockSupport.unpark(waiters.peek());
 }
}

这是一个先入先出的锁,也就是只有队列的头部线程才能获取锁,如果当前线程不是头部元素,或者锁已经被其他线程获取,则调用park方法阻塞自己。

这里的wasInterrupted怎么理解呢?如果park方法是因为被中断而返回的,则忽略中断,只记录wasInterrupted=true。因为这说明不是其他线程调用了解锁方法unlock()里面的unpark让它返回的,它还不能获取锁,得继续阻塞。虽然当前线程对中断标志不感兴趣,但不代表其他线程也不敢兴趣,所以在成功获取锁后,重新对自己调用interrupt,恢复下中断标志的值。

参考资料:

《Java并发编程之美》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值