Unsafe和LockSupport
Java并发包下的类基本都是基于AQS (AbstractQueuedSynchronizer)框架实现的,关于AQS我在前面讲解ReentrantLock源码的文章中就已经有涉及了。
-->> 面试难点:深度解析ReentrantLock的实现原理
而AQS线程安全的实现,又是基于两个很关键的类Unsafe和LockSupport,其中Unsafe主要直接提供CAS操作(关于cas,在文章 读懂AtomicInteger源码(多线程专题 )中讲解过 ),LockSupport主要提供park/unpark操作,而park/unpark最终调用还是unsafe类,所以unsafe类才是关键。
(如果不会下载JVM源码可以后台回复 “ jdk ”,获得下载压缩包)
public static void park() {
UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
Unsafe类实现:
//park
public native void park(boolean isAbsolute, long time);
//unpack
public native void unpark(Object var1);
由代码可见,Unsafe类的park/unpark是native级别的实现。使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
park函数是将当前调用线程阻塞,unpark函数则是将指定线程线程唤醒。
park和unpark作用:
park是等待一个许可,unpark是为某线程提供一个许可。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。每次调用一次park,需要有一个unpark来解锁。
并且unpark可以先于park调用,但是不管unpark先调用几次,都只提供一个许可,不可叠加,只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。
Unsafe.park源码
在Linux系统下,park和unpark是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
简单来说,mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。当_counter=0 时线程阻塞,当_counter>0直接设为0并返回。
每个Java线程都有一个Parker实例,Parker类部分源码如下:
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
class PlatformParker : public CHeapObj {
protected:
pthread_mutex_t _mutex [1] ;
pthread_cond_t _cond [1] ;
...
}
由源码可知Parker类继承于PlatformParker,实际上时用Posix的mutex,condition来实现的。Parker类里的_counter字段,就是用来记录park和unpark是否需要阻塞的标识。
执行过程
具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查_counter是不是大于0,如果是,则把_counter设置为0,返回。如果等于零,继续执行,阻塞等待。
void Parker::park(bool isAbsolute, jlong time) {
//判断信号量counter是否大于0,如果大于设为0返回
if (Atomic::xchg(0, &_counter) > 0) return;
//获取当前线程
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;
//如果中途已经是interrupt了,那么立刻返回,不阻塞
// Check interrupt before trying to wait
if (Thread::is_interrupted(thread, false)) {
return;
}
//记录当前绝对时间戳
// Next, demultiplex/decode time arguments
timespec absTime;
//如果park的超时时间已到,则返回
if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
return;
}
//更换时间戳
if (time > 0) {
unpackTime(&absTime, isAbsolute, time);
}
// Enter safepoint region
// Beware of deadlocks such as 6317397.
// The per-thread Parker:: mutex is a classic leaf-lock.
// In particular a thread must never block on the Threads_lock while
// holding the Parker:: mutex. If safepoints are pending both the
// the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
//进入安全点,利用该thread构造一个ThreadBlockInVM
ThreadBlockInVM tbivm(jt);
// Don't wait if cannot get lock since interference arises from
// unblocking. Also. check interrupt before trying wait
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
//记录等待状态
int status ;
//中途再次检查许可,有则直接返回不等带。
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();
return;
}
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
assert(_cur_index == -1, "invariant");
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
//线程条件等待 线程等待信号触发,如果没有信号触发,无限期等待下去。
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;
assert_status(status == 0 || status == EINTR ||
status == ETIME || status == ETIMEDOUT,
status, "cond_timedwait");
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();
// If externally suspended while waiting, re-suspend
if (jt->handle_special_suspend_equivalent_condition()) {
jt->java_suspend_self();
}
}
Unsafe.unpark源码
unpark直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。
源码如下:
void Parker::unpark() {
//定义两个变量,staus用于判断是否获取锁
int s, status ;
//获取锁
status = pthread_mutex_lock(_mutex);
//判断是否成功
assert (status == 0, "invariant") ;
//存储原先变量_counter
s = _counter;
//把_counter设为1
_counter = 1;
if (s < 1) {
// thread might be parked
if (_cur_index != -1) {
// thread is definitely parked
if (WorkAroundNPTLTimedWaitHang) {
status = pthread_cond_signal (&_cond[_cur_index]);
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[_cur_index]);
assert (status == 0, "invariant");
}
} else {
//释放锁
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
} else {
//释放锁
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
(如果不会下载JVM源码可以后台回复 “ jdk ”,获得下载压缩包)
◆ ◆ ◆ ◆ ◆
关注并后台回复 “面试” 或者 “视频”,
即可免费获取最新2019BAT
大厂面试题和大数据微服务视频
您的分享和支持是我更新的动力
·END·
后端开发技术
追求技术的深度
微信号:后端开发技术觉得不错“在看”支持一下~
↓↓↓