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源码分析的主要内容,如果未能解决你的问题,请参考以下文章
提升--08---LockSupport淘宝面试题与源码阅读方法论