类的属性

  1. public class LockSupport {
  2. // Hotspot implementation via intrinsics API
  3. private static final sun.misc.Unsafe UNSAFE;
  4. // 表示内存偏移地址
  5. private static final long parkBlockerOffset;
  6. // 表示内存偏移地址
  7. private static final long SEED;
  8. // 表示内存偏移地址
  9. private static final long PROBE;
  10. // 表示内存偏移地址
  11. private static final long SECONDARY;
  12. static {
  13. try {
  14. // 获取Unsafe实例
  15. UNSAFE = sun.misc.Unsafe.getUnsafe();
  16. // 线程类类型
  17. Class<?> tk = Thread.class;
  18. // 获取Thread的parkBlocker字段的内存偏移地址
  19. parkBlockerOffset = UNSAFE.objectFieldOffset
  20. (tk.getDeclaredField("parkBlocker"));
  21. // 获取Thread的threadLocalRandomSeed字段的内存偏移地址
  22. SEED = UNSAFE.objectFieldOffset
  23. (tk.getDeclaredField("threadLocalRandomSeed"));
  24. // 获取Thread的threadLocalRandomProbe字段的内存偏移地址
  25. PROBE = UNSAFE.objectFieldOffset
  26. (tk.getDeclaredField("threadLocalRandomProbe"));
  27. // 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
  28. SECONDARY = UNSAFE.objectFieldOffset
  29. (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
  30. } catch (Exception ex) { throw new Error(ex); }
  31. }
  32. }

核心函数分析

Unsafe 的 park 和 unpark 函数

  1. park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: ① 调用 unpark 函数,释放该线程的许可。② 该线程被中断。③ 设置的时间到了。并且,当 time 为绝对时间时,isAbsolute 为 true,否则,isAbsolute 为 false。当 time 为 0 时,表示无限等待,直到 unpark 发生。
  2. unpark 函数,释放线程的许可,即激活调用 park 后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。

    park 函数

    park 用于挂起当前线程,如果许可可用,会立即返回,并消费掉许可。
    park 函数有两个重载版本,摘要如下:
    1. public static void park();
    2. public static void park(Object blocker);
    park(Object) 实现如下: ```java // java.util.concurrent.locks.LockSupport#park(java.lang.Object)

public static void park() { // 获取许可,设置时间为无限长,直到可以获取许可 UNSAFE.park(false, 0L); }

public static void park(Object blocker) { // #1 获取当前线程 Thread t = Thread.currentThread();

  1. // #2 设置 Blocker
  2. setBlocker(t, blocker);
  3. // #3 获取许可
  4. UNSAFE.park(false, 0L);
  5. // #4 重新可运行后再将线程的 Blocker 恢复为 null
  6. setBlocker(t, null);

}

private static void setBlocker(Thread t, Object arg) { // 设置线程t的parkBlocker字段的值为arg UNSAFE.putObject(t, parkBlockerOffset, arg); }

疑问:为什么要在 park(Object) 中两次调用 `setBlocker()` 函数呢? 主要是恢复线程。<br />每个线程都有一个 `volatile Object parkBlocker` ,专门服务于 `LockSupport` 工具类。<br />调用 `park()` 函数后,会将目标线程阻塞,当以下情况发生时,意味着许可可用:

1. 其它某个线程将当前线程作为目标调用 `unpark()` 函数。
1. 其它某个线程中断当前线程。
1. `absolute = false && time != 0`,过了给定的时间后线程被唤醒。
1. 虚假唤醒。
<a name="pdQrO"></a>
## unpark 函数
此函数表示如果给定的线程的许可尚不可用,则使其可用。如果线程阻塞于 park() 函数调用,则它将被解除阻塞状态。
<a name="AzTZM"></a>
# Demo
```java
// 获取许可,阻塞、可中断响应
LockSupport.park("ParkAndUnparkDemo");
import java.util.concurrent.locks.LockSupport;

class MyThread extends Thread {
    private Object object;

    public MyThread(Object object) {
        this.object = object;
    }

    public void run() {
        System.out.println("before unpark");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取blocker
        System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
        // 释放许可
        LockSupport.unpark((Thread) object);
        // 休眠500ms,保证先执行park中的setBlocker(t, null);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再次获取blocker
        System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));

        System.out.println("after unpark");
    }
}

public class test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        myThread.start();
        System.out.println("before park");
        // 获取许可
        LockSupport.park("ParkAndUnparkDemo");
        System.out.println("after park");
    }
}

深入理解

Thread.sleep() 和 Object.wait() 的区别

API 是否释放锁 入参
Thread.sleep() 不会 必须传入时间
Object.wait 可传可不传,不传表示一直阻塞

Thread.sleep() 和 Condition.await() 的区别

Object.wait() 和 Condition.await() 的原理是基本一致的,不同的是 Condition.await() 底层是调用 LockSupport.park() 来实现阻塞当前线程的。 实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让 state 状态变量变为 0,然后才是调用 LockSupport.park() 阻塞当前线程。

Thread.sleep() 和 LockSupport.park() 的区别

LockSupport.park() 还有几个兄弟方法—— parkNanos()、parkUtil() 等,我们这里说的 park() 方法统称这一类方法。

  • 阻塞当前线程且都不释放锁。
  • Thread.sleep() 没法从外部唤醒,只能超时后被系统唤醒。LockSupport.park() 方法可以被另一个线程调用 LockSupport.unpark() 方法唤醒。
  • Thread.sleep() 方法声明上抛出了 InterruptedException 中断异常,所以调用者需要捕获这个异常或者再抛出。LockSupport.park() 方法不需要捕获中断异常。
  • Thread.sleep() 本身就是一个 native 方法,LockSupport.park() 底层是调用的 Unsafe 的 native 方法。

    Object.wait() 和 LockSupport.park() 的区别

    二者都会阻塞当前线程的运行,他们有什么区别呢?

  • 是否需要持有锁:Object.wait() 方法需要在 synchronized 块中执行,即得先持有锁,才能 wait,否则抛出异常。LockSupport.park() 可以在任意地方执行。

  • 是否需要处理受检查异常:Object.wait() 方法声明抛出了中断异常,调用者需要捕获或者再抛出。LockSupport.park() 不需要捕获中断异常
  • 是否继续执行后续代码:Object.wait() 不带超时的,需要另一个线程执行 notify() 来唤醒,但不一定继续执行后续代码。LockSupport.park() 不带超时的,需要另一个线程执行 unpark() 来唤醒,一定会继续执行后续代码。
  • 如果在 wait() 之前执行了 notify() 会怎样? 抛出 IllegalMonitorStateException 异常。
  • 如果在 park() 之前执行了 unpark() 会怎样? 线程不会被阻塞,直接跳过 park(),继续执行后续内容。park()/unpark() 底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的 Semaphore,只不过这个信号量在重复执行 unpark() 的时候也不会再增加许可证,最多只有一个许可证。

    LockSupport.park() 会释放锁资源吗?

    不会,它只负责阻塞当前线程,释放锁资源实际上是在 Condition 的 await() 方法中实现的。

    中断

    主线程因为调用 park() 而被阻塞,而另一个线程因中断而退出,此时主线程被唤醒,继续执行下面代码。可见,interrupt 的作用和 unpark 是一样的。

原理分析

LockSupport 类可以阻塞当前线程以及唤醒指定被阻塞的线程。主要是通过 park() 和 unpark(thread) 方法来实现阻塞和唤醒线程的操作的。

  1. 每个线程都有一个许可 (permit),permit 只有两个值 1 和 0,默认是 0。
  2. 当调用 unpark(thread) 方法,就会将 thread 线程的许可 permit 设置成 1(注意多次调用 unpark 方法,不会累加,permit 值还是 1)。
  3. 当调用 park() 方法,如果当前线程的 permit 是 1,那么将 permit 设置为 0,并立即返回。如果当前线程的 permit 是 0,那么当前线程就会阻塞,直到别的线程将当前线程的 permit 设置为 1。
  4. park 方法会将 permit 再次设置为 0,并返回。注意:因为 permit 默认是 0,所以一开始调用 park() 方法,线程必定会被阻塞。调用 unpark(thread) 方法后,会自动唤醒 thread 线程,即 park 方法立即返回。

park 系统方法会使线程进入 WAITING 状态或 TIMED_WAITING 等待状态。调用 unpark() 方法或中断都可以唤醒线程。
Blocker 的好处在于,在诊断问题的时候能够知道 park 的原因,推荐使用带 Object 的 park() 操作。

总结

  • park/unpark能够精准的对线程进行唤醒和等待。
  • linux上的实现是通过POSIX的线程API的等待、唤醒、互斥、条件来进行实现的
  • park在执行过程中首选看是否有许可,有许可就立马返回,而每次unpark都会给许可设置成有,这意味着,可以先执行unpark,给予许可,再执行park立马自行,适用于producer快,而consumer还未完成的场景参考地址