栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

我相信有很多人都知道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(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 );

      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秒
来实现线程的停顿

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/781528.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号