0
点赞
收藏
分享

微信扫一扫

是使用Thread.sleep(200) 还是使用 LockSupport.parkNanos(200*1000*1000l) ?

凶猛的小白兔 2022-03-30 阅读 73


相信这个问题很少人会想过问这个问题,但是看到相关文章后定然是新奇。

我相信有很多人都知道​​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秒​

来实现线程的​​停顿​



举报

相关推荐

0 条评论