LockSupport源码分析
概述
LockSupport类是基于Unsafe实现的用来阻塞和唤醒线程的工具类,它和wait,notify方法在功能上是相似的,只不过LockSupport提供了更为直观的阻塞线程语义以及能够精确的唤醒具体哪个线程,而notify只能随机唤醒某个线程。
Unsafe中的实现
LockSupport阻塞唤醒线程是通过调用Unsafe类中的park,unpark和putObject方法来实现的
1 | public native void park(boolean var1, long var2); |
- park方法中的第一个布尔类型的参数代表是否是绝对时间,它是相对第二个long类型的时间参数,如果时间参数为0的话,线程就会一直被阻塞
- unpark的参数是Thread实例,它的作用是用来唤醒这个被阻塞的线程
- putObject方法中的第一个参数是线程实例,第二个参数是Thread类中的
volatile Object parkBlocker;
在内存的偏移,第三个参数是一个实例对象锁,调用该方法会将Thread中的parkBlocker设置为这个对象。
阻塞方法
在LockSupport中一共定义了六个阻塞线程的方法
public static void park()
1 | public static void park() { |
调用该方法的线程会一直被阻塞
public static void park(Object blocker)
1 | public static void park(Object blocker) { |
调用该方法的线程会被阻塞在blocker对象上,是通过内部的setBlocker方法来给当前线程设置parkBlocker的
1 | private static void setBlocker(Thread t, Object arg) { |
最终调用的是Unsafe类的putObject方法,设置当前线程的parkBlocker成员变量为arg,然后接着执行UNSAFE.park(false, 0L);
这行代码后,当前线程就被阻塞在这一行了,在其它线程唤醒当前线程后接着执行setBlocker(t, null);
这行代码,清除当前线程的parkBlocker
public static void parkNanos(long nanos)
1 | public static void parkNanos(long nanos) { |
调用该方法的线程会被阻塞指定纳秒的时间,然后自动恢复运行
public static void parkNanos(Object blocker, long nanos)
1 | public static void parkNanos(Object blocker, long nanos) { |
调用该方法的线程会被阻塞在blocker对象上指定纳秒的时间,然后自动恢复运行
public static void parkUntil(long deadline)
1 | public static void parkUntil(long deadline) { |
调用该方法的线程会被阻塞直到指定的时间为止,随即会被自动唤醒,参数deadline是从纪元开始的绝对时间,单位是毫秒
public static void parkUntil(Object blocker, long deadline)
1 | public static void parkUntil(Object blocker, long deadline) { |
调用该方法的线程会被阻塞在blocker对象上直到指定的时间为止,随即会被自动唤醒,参数deadline是从纪元开始的绝对时间,单位是毫秒
唤醒方法
在LockSupport中只有一个唤醒线程的方法
1 | public static void unpark(Thread thread) { |
唤醒指定的线程
许可证
LockSupport是用于创建锁和其他同步类的基本线程阻塞原语。该类与使用它的每个线程关联一个许可证。这有点类似生产者和消费者的设计理念。当调用park方法的时候(Consumer),当前线程会去获取一个和自身关联的许可证,如果拿不到这个许可证线程就会被阻塞。调用unpark方法的时候(Producer),会给当前线程设置一个许可证。值得注意的一点是unpark方法可以在park方法之前调用,也就是说如果执行顺序为
1 | LockSupport.unpark(Thread.currentThread()); |
这时线程并不会被阻塞,需要调用两次pack方法线程才会被阻塞
1 | LockSupport.unpark(Thread.currentThread()); |
以上代码执行后当前线程会被阻塞,说明LockSupport是通过一个许可证来标记线程是否被阻塞的,当然了这个许可证是不能被叠加的,多次调用unpark方法,线程依旧只会拥有一个许可证
1 | LockSupport.unpark(Thread.currentThread()); |
此时线程依旧会被阻塞。
例子
1 | package com.example.demo; |
控制台输出
1 | worker第一次被唤醒15:00:33.282 |
- 开启一个worker线程,分别通过
park()
,park(Object blocker)
,parkUntil(long deadline)
阻塞该线程 - 在main线程中通过
parkNanos(long nanos)
方法每隔2秒唤醒worker线程
总结
LockSupport相比于传统的wait和notify机制能够精确的唤醒具体哪个线程,并且提供的阻塞唤醒方法更为直观可理解。并且它的实现方式是类比生产者消费者模式的,在调用阻塞和唤醒方法时分别需要消费和生产一个许可证。