LockSupport是JUC包下的一个工具类,主要作用是用于阻塞和唤醒线程,底层基于Unsafe类实现。LockSupport类的所有方法都被static修饰,可以在任意位置阻塞或唤醒某线程。
JUC包下的队列同步器AQS的阻塞和唤醒操作就是使用LockSupport实现。接下来关注LockSupport的源码。
LockSupport类只提供了一个被private修饰的构造方法,意味着LockSupport不能在任何地方被实例化,但所有方法都是静态方法,可以在任意地方被调用。
private LockSupport() {}
成员变量中重点关注parkBlockerOffset变量,即blocker的偏移量,下文讲解。
private LockSupport() {}
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
/** 获取一个Unsafe实例 后续利用该实例获取偏移量 */
UNSAFE = sun.misc.Unsafe.getUnsafe();
/** 创建一个Thread的class对象 后续利用反射获取字段 */
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
在分析LockSupport源码前,先来解决上一部分遗留的问题,什么是blocker,blocker的作用是什么?
在Thread类的源码中,可以找到这样一个被volatile修饰的变量,LockSupport类中所有blocker的相关变量以及方法都是为这个parkBlocker变量服务的。
volatile Object parkBlocker;
当线程被阻塞时,如果该线程的parkBlocker变量不为空,则在打印堆栈异常时,控制台会打印输出具体阻塞对象的信息,方便错误排查,后文会对此进行演示
首先关注blocker相关方法:
private static void setBlocker(Thread t, Object arg) {
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
源码中可以清晰的看到方法中使用了parkBlockerOffset,即在类初始化时获取的parkBlocker在内存中的偏移量,putObject和getObjectVolatile方法采用地址加偏移量的方式从内存直接设置或获取parkBlocker(Unsafe包下的方法可以直接操作内存,因此该类被命名为Unsafe),这样做的原因是因为线程被阻塞时无法被赋值或取值。
LockSupport为阻塞操作提供了两组三类方法
一组是不设置blocker的方法,另一组是设置blocker方法的,JDK推荐为Thread设置blocker方便调试。其余分析见注释。
/** 基础阻塞方法,无时限 */
public static void park(Object blocker) {
/** 获取当前线程 */
Thread t = Thread.currentThread();
setBlocker(t, blocker);
/** 阻塞核心方法 直到被唤醒前不会执行下一语句 内部逻辑后文展开讨论 */
UNSAFE.park(false, 0L);
/** 线程被唤醒后parkBlocker重新置null */
setBlocker(t, null);
}
/** 逻辑与park基本相同 阻塞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);
}
}
/** 逻辑与park基本相同 阻塞到日期deadline为止 超过deadline日期后自动被唤醒 */
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
public static void park() {
UNSAFE.park(false, 0L);
}
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
unpark方法如下,UNSAFE.unpark为唤醒线程的核心方法,后文讨论。
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
(blocker学习自Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类)
首先测试不设置blocker阻塞线程时,用jstack指令打印结果(首先在idea的terminal窗口输入jsp,找到当前运行类的pid,之后输入jstack pid即可打印进程堆栈):
public class LockSupportTest {
public void park(){
LockSupport.park();
}
public static void main(String[] args) {
new LockSupportTest().park();
}
}
public class LockSupportTest {
public void park(){
LockSupport.park(this);
}
public static void main(String[] args) {
new LockSupportTest().park();
}
}
在Unsafe的源码中可见,park与unpark都是native方法(意味着由C++底层实现),具体实现细节需要阅读C++源码
阅读C++源码前首先简述一下使用park和unpark阻塞和唤醒的思想,park/unpark的核心是一个抽象概念——许可。许可的具体实现为一个二元整型变量,即该变量只有0和1两个状态,默认值为0。当一个线程调用了park的时候,如果许可仍有剩余(为1),则当前线程不阻塞(持有许可),如果许可无剩余(为0),则当前线程阻塞等待获取许可,相应的调用unpark时会释放许可(置1)。
由于作者C++水平有限且本文关注重点在java,所以省略部分非关键代码,只看核心实现。
void Parker::park(bool isAbsolute, jlong time) {
// 先原子的将_counter的值设为0,并返回_counter的原值,如果原值>0说明有通行证,直接返回
// 首先尝试能否获取许可
// 利用原子操作设_counter(许可)为0,同时返回_counter原值
// 原值为1时获取许可成功 直接return;
if (Atomic::xchg(0, &_counter) > 0) return;
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;
//如果线程被中断 直接返回
if (Thread::is_interrupted(thread, false)) {
return;
}
timespec absTime;
// 如果出现time小于0 或
// 调用了parkUntil方法(只用调用parkUntil时isAbsolute为true)且time为0的情况
// 意味着不需要尝试获取许可 直接返回
if (time < 0 || (isAbsolute && time == 0) ) {
return;
}
// 只有在java种调用了parkNanos或parkUntil方法才会进入此分支
if (time > 0) {
// 定时唤醒
unpackTime(&absTime, isAbsolute, time);
}
ThreadBlockInVM tbivm(jt);
// 如果线程被中断,直接返回
// 如果没有被中断,且获取互斥锁失败,直接返回
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
int status ;
// 如果_counter > 0, 不需要等欧拉欧拉待,这里再次检查_counter的值
if (_counter > 0) {
_counter = 0;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
// 插入写屏障
OrderAccess::fence();
return;
}
OSThreadWaitState osts(thread->osthread(), false);
// 暂停Java线程
jt->set_suspend_equivalent();
assert(_cur_index == -1, "invariant");
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
// 线程进入阻塞状态 并等待_cond[_cur_index]信号
status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
} else {
_cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
// 线程进入限时阻塞状态
status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
if (status != 0 && WorkAroundNPTLTimedWaitHang) {
pthread_cond_destroy (&_cond[_cur_index]) ;
pthread_cond_init (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
}
}
_cur_index = -1;
_counter = 0 ;
// 互斥锁释放
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
OrderAccess::fence();
}
void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
//保存原始许可值 用于后续判断
s = _counter;
//许可置1
_counter = 1;
if (s < 1) {
if (WorkAroundNPTLTimedWaitHang) {
//唤醒等待线程
status = pthread_cond_signal (_cond) ;
assert (status == 0, "invariant") ;
//释放锁操作
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
} else {
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
status = pthread_cond_signal (_cond) ;
assert (status == 0, "invariant") ;
}
} else {
//释放锁
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
以上便是本篇文章的全部内容
作者才疏学浅,如文中出现纰漏,还望指正