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 = sun.misc.Unsafe.getUnsafe(); 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); } }关键方法 blocker的作用
在分析LockSupport源码前,先来解决上一部分遗留的问题,什么是blocker,blocker的作用是什么?
在Thread类的源码中,可以找到这样一个被volatile修饰的变量,LockSupport类中所有blocker的相关变量以及方法都是为这个parkBlocker变量服务的。
volatile Object parkBlocker;
当线程被阻塞时,如果该线程的parkBlocker变量不为空,则在打印堆栈异常时,控制台会打印输出具体阻塞对象的信息,方便错误排查,后文会对此进行演示
blocker相关方法首先关注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),这样做的原因是因为线程被阻塞时无法被赋值或取值。
park与unpark方法LockSupport为阻塞 *** 作提供了两组三类方法
一组是不设置blocker的方法,另一组是设置blocker方法的,JDK推荐为Thread设置blocker方便调试。其余分析见注释。
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } 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); } } 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测试
(blocker学习自Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类)
阻塞时不设blocker首先测试不设置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)。
park在C++的源码实现由于作者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(); }unpark在C++的源码实现
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") ; } }park/unpark的特点及与wait/notify的区别
- 先唤醒再阻塞 *** 作,由LockSupport实现,即先调用unpark再调用park,则该线程不会被阻塞;由Object实现,即先调用notify再调用wait,则该线程会被阻塞。
- LockSupport允许在任意地方阻塞唤醒线程,Object的wait/notify必须在synchronized同步代码块内调用。因为park/unpark依赖许可量,wait/notify依赖锁。
- LockSupport允许唤醒指定线程,notify只能唤醒随机线程,notifyAll唤醒全部阻塞线程。
以上便是本篇文章的全部内容
作者才疏学浅,如文中出现纰漏,还望指正
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)