相信这个问题很少人会想过问这个问题,但是看到相关文章后定然是新奇。
我相信有很多人都知道LockSupport
这个工具类,但绝大多数人学JUC
都只是处于应用JUC
。没有对底层原理思考。
那么在我提出这个问题的时候,你能说出其中的一二吗?
Thread.sleep
与 LockSupport.parkNanos
两者现象都能让线程暂停下来,但是底层的原理有所区别。
在java类库中2个方法都标有相应的注释,注释中解释到: sleep 虽然让线程暂停了,但是不会释放资源,而LockSupport.park
会释放资源。
在看完部分jdk底层代码后,大致上可以看出一些内容,实际上2者在jdk中都由JavaThread
对象进行调用。
一、LockSupport对于Park的实现
LockSupport是由Unsafe实现的对应openJDK源码位置 src/hotspot/share/prims/unsafe.cpp
parkNanos
调用 park
然后 最终调用是
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time)) {
HOTSPOT_THREAD_PARK_BEGIN((uintptr_t) thread->parker(), (int) isAbsolute, time);
EventThreadPark event;
JavaThreadParkedState jtps(thread, time != 0);
thread->parker()->park(isAbsolute != 0, time);
if (event.should_commit()) {
const oop obj = thread->current_park_blocker();
if (time == 0) {
post_thread_park_event(&event, obj, min_jlong, min_jlong);
} else {
if (isAbsolute != 0) {
post_thread_park_event(&event, obj, min_jlong, time);
} else {
post_thread_park_event(&event, obj, time, min_jlong);
}
}
}
HOTSPOT_THREAD_PARK_END((uintptr_t) thread->parker());
} UNSAFE_END
这里是上面调用的那个方法,在源码中就在Unsafe_Park上面
static void post_thread_park_event(EventThreadPark* event, const oop obj, jlong timeout_nanos, jlong until_epoch_millis) {
assert(event != NULL, "invariant");
assert(event->should_commit(), "invariant");
event->set_parkedClass((obj != NULL) ? obj->klass() : NULL);
event->set_timeout(timeout_nanos);
event->set_until(until_epoch_millis);
event->set_address((obj != NULL) ? (u8)cast_from_oop<uintptr_t>(obj) : 0);
event->commit();
}
post_thread_park_event
从名称就可以大致看出它的实现是调用JavaThread
的PrakEvent
实现(与下面的Thread.sleep
)一致
二、Thread对于sleep的实现
需要一点耐心,看下注释和代码的命名,见名知意。
大致可以看出 sleep是一个自旋+park
实现
源码位置:src/hotspot/share/runtime/thread.cpp
// java.lang.Thread.sleep support
// Returns true if sleep time elapsed as expected, and false
// if the thread was interrupted.
bool JavaThread::sleep(jlong millis) {
assert(this == Thread::current(), "thread consistency check");
ParkEvent * const slp = this->_SleepEvent;
// Because there can be races with thread interruption sending an unpark()
// to the event, we explicitly reset it here to avoid an immediate return.
// The actual interrupt state will be checked before we park().
slp->reset();
// Thread interruption establishes a happens-before ordering in the
// Java Memory Model, so we need to ensure we synchronize with the
// interrupt state.
OrderAccess::fence();
jlong prevtime = os::javaTimeNanos();
for (;;) {
// interruption has precedence over timing out
if (this->is_interrupted(true)) {
return false;
}
if (millis <= 0) {
return true;
}
{
ThreadBlockInVM tbivm(this);
OSThreadWaitState osts(this->osthread(), false /* not Object.wait() */);
this->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or
// java_suspend_self() via check_and_wait_while_suspended()
slp->park(millis);
// were we externally suspended while we were waiting?
this->check_and_wait_while_suspended();
}
// Update elapsed time tracking
jlong newtime = os::javaTimeNanos();
if (newtime - prevtime < 0) {
// time moving backwards, should only happen if no monotonic clock
// not a guarantee() because JVM should not abort on kernel/glibc bugs
assert(!os::supports_monotonic_clock(),
"unexpected time moving backwards detected in JavaThread::sleep()");
} else {
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
}
prevtime = newtime;
}
}
看完2者的实现后感觉sleep有占用cpu时间片的情况(自旋更新park剩余时间
)只能被中断休眠,否则会一直自旋直到休眠结束从cpp源码中也可以看出,中断后就会return false结束自旋
。
对于LockSupport 看起来效率更高,但存在虚假唤醒的可能(当然park的线程是可以被unpark唤醒的)
当然如果我们只是想要让线程暂停一会的话,在不考虑虚假唤醒的情况下,使用LockSupport的效率更高。
虚假唤醒
实际上虚假唤醒并非不可控,这里面的"虚假"
一词并非说一个值为0的数突然变成了1,实际上"虚假"
是人为造成的,而之所以产生这一词是因为Object.notifyAll()
会唤醒所有线程,有些线程我们并不像唤醒它,但是因为与Object.notifyAll()
类似,导致出现本不该唤醒的线程被唤醒了。故出现了虚假唤醒现象,实际上在我们平时写代码的过程中,如果我们对于自己的代码逻辑非常确定不存在虚假唤醒的线程,那么大可以放心的使用LockSupport.parkNanos
、LockSupport.parkUntil
、Object.notifyAll()
等存在虚假唤醒的代码。
测试代码
为了更好的理解建议运行一下下面的代码,可以看下效果
import java.time.Instant;
import java.util.concurrent.locks.LockSupport;
public class Demo {
private final static Thread sleepThread =new Thread(()->{
try {
Thread.sleep(20*1000);// 休眠20秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程从sleep被唤醒");
});
private final static Thread parkThread =new Thread(()->{
LockSupport.parkUntil(Instant.now().toEpochMilli()+20000);//休眠10秒
System.out.println("线程从park状态中被唤醒");
});
public static void main(String[] args) {
// sleepThread.start();
parkThread.start();
new Thread(()->{
System.out.println("3秒后唤醒休眠线程");
LockSupport.parkUntil(Instant.now().toEpochMilli()+3000);// 休眠3秒
System.out.println("开始唤醒线程");
LockSupport.unpark(parkThread);
// try {
// sleepThread.interrupt();
// } catch (Exception e) {
// e.printStackTrace();
// }
}).start();
}
}
其实作者我只是想告诉大家,平时除了写Thread.sleep(200)
外
还可以使用LockSupport.parkUntil(Instant.now().toEpochMilli()+3000);// 休眠3秒
java8新的时间类
还可以使用LockSupport.parkNanos(3*1000*1000*1000l) ;// 休眠3秒
来实现线程的停顿