Java同步器之LockSupport源码解析
一、简介
LockSupport
是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport
中的park()
和unpark()
的作用分别是阻塞线程和解除阻塞线程,而且park()
和unpark()
不会遇到“Thread.suspend
和Thread.resume
所可能引发的死锁”问题。因为park()
和unpark()
有许可的存在;调用park()
的线程和另一个试图将其unpark()
的线程之间的竞争将保持活性。
二、源码
2.1 构造方法
LockSupport
类只提供了一个被private
修饰的构造方法,意味着LockSupport
不能在任何地方被实例化,但所有方法都是静态方法,可以在任意地方被调用。
/**
* 私有构造
*/
private LockSupport() {}
2.2 属性
/**
* 这个方法是由于多线程随机数生成器ThreadLocalRandom的package访问权限限制不能被这个包下的类使用,
* 复制了一份实现出来,在StampedLock中被使用
*/
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
r = 1; // avoid zero
UNSAFE.putInt(t, SECONDARY, r);
return r;
}
/**
* /**
* * 三种情况停止阻塞:
* * 1. 调用unpark
* * 2. 线程被中断
* * 3. 设置时间到了
* * @Param isAbsolute 是否绝对时间
* * @Param time 时间 为0时代表无线等待
* *
* Unsafe.park(boolean isAbsolute,long time);
*
* /**
* * 释放某线程,需要保证释放时线程存活
* *
* Unsafe.unpark(Thread thread);
*/
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); }
}
2.3 blocker
2.3.1 blocker的作用
在分析LockSupport
源码前,什么是blocker
,blocker
的作用是什么?
在Thread
类的源码中,可以找到这样一个被volatile
修饰的变量,LockSupport
类中所有blocker
的相关变量以及方法都是为这个parkBlocker
变量服务的。
volatile Object parkBlocker;
当线程被阻塞时,如果该线程的parkBlocker
变量不为空,则在打印堆栈异常时,控制台会打印输出具体阻塞对象的信息,方便错误排查,后文会对此进行演示
2.3.2 blocker相关方法
首先关注blocker
相关方法:
/**
* 设置blocker
* 将线程t的parkBlocker字段的值更新为arg
*/
private static void setBlocker(Thread t, Object arg) {
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
/**
* 获取blocker
*/
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
源码中可以清晰的看到方法中使用了parkBlockerOffset
,即在类初始化时获取的parkBlocker
在内存中的偏移量,putObject
和getObjectVolatile
方法采用地址加偏移量的方式从内存直接设置或获取parkBlocker
(Unsafe
包下的方法可以直接操作内存,因此该类被命名为Unsafe
),这样做的原因是因为线程被阻塞时无法被赋值或取值。
2.4 park
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);
}
/**
* 无限等待其它线程将该线程unpark或中断该线程
*/
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);
}
2.5 unpark
unpark
方法如下,UNSAFE.unpark
为唤醒线程的核心方法
/**
* 释放许可,使线程继续运行。
* 如果线程没被阻塞,则下次park不会阻塞该线程?
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
说明:LockSupport
是通过调用Unsafe
函数中的接口实现阻塞和解除阻塞的。
2.6 底层实现
在Unsafe
的源码中可见,park
与unpark
都是native
方法(意味着由C++底层实现)
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") ;
}
}
三、示例
对比下面的“示例1”和“示例2”可以更清晰的了解LockSupport
的用法。
示例1
public class WaitTest1 {
public static void main(String[] args) {
ThreadA ta = new ThreadA("ta");
synchronized(ta) { // 通过synchronized(ta)获取“对象ta的同步锁”
try {
System.out.println(Thread.currentThread().getName() + " start ta");
ta.start();
System.out.println(Thread.currentThread().getName() + " block");
// 主线程等待
ta.wait();
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public void run() {
synchronized (this) { // 通过synchronized(this)获取“当前对象的同步锁”
System.out.println(Thread.currentThread().getName() + " wakup others");
notify(); // 唤醒“当前对象上的等待线程”
}
}
}
}
示例2
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest1 {
private static Thread mainThread;
public static void main(String[] args) {
ThreadA ta = new ThreadA("ta");
// 获取主线程
mainThread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " start ta");
ta.start();
System.out.println(Thread.currentThread().getName() + " block");
// 主线程阻塞
LockSupport.park(mainThread);
System.out.println(Thread.currentThread().getName() + " continue");
}
static class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " wakup others");
// 唤醒“主线程”
LockSupport.unpark(mainThread);
}
}
}
运行结果 :
main start ta
main block
ta wakup others
main continue
四、总结
LocalSupport
也是实现线程间通信的一种有效的方式。unpark
和park
之间不需要有严格的顺序。可以先执行unpark
,之后再执行park
。这样再编码的过程中就比使用wait/notify
方法要简单很多。此外,LocalSUpport
全部是采用UnSafe
类来实现的。这个类通过使用park/unpark
以及相关cas
操作,就实现了java
中JUC
的各种复杂的数据结构和容器。而且效率非常高。
五、拓展
5.1 park/unpark与wait/notify的区别
- 先唤醒再阻塞操作,由
LockSupport
实现,即先调用unpark
再调用park
,则该线程不会被阻塞;由Object
实现,即先调用notify
再调用wait
,则该线程会被阻塞。 LockSupport
允许在任意地方阻塞唤醒线程,Object
的wait/notify
必须在synchronized
同步代码块内调用。因为park/unpark
依赖许可量,wait/notify
依赖锁。LockSupport
允许唤醒指定线程,notify
只能唤醒随机线程,notifyAll
唤醒全部阻塞线程。