让面试官心服口服:Thread.sleep、synchronized、LockSupport.park的线程阻塞有何区别?

前言

在日常编码的过程中,我们经常会使用Thread.sleep、LockSupport.park()主动阻塞线程,或者使用synchronized和Object.wait来阻塞线程保证并发安全。此时我们会发现,对于Thread.sleep和Object.wait方法是会抛出InterruptedException,而LockSupport.park()和synchronized则不会。而当我们调用Thread.interrupt方法时,除了synchronized,其他线程阻塞的方式都会被唤醒。

于是本文就来探究一下Thread.sleep、LockSupport.park()、synchronized和Object.wait的线程阻塞的原理以及InterruptedException的本质

本文主要分为以下几个部分

1.Thread.sleep的原理

2.LockSupport.park()的原理

3.synchronized线程阻塞的原理

4.ParkEvent和parker对象的原理

5.Thread.interrupt的原理

6.对于synchronized打断原理的扩展

1.Thread.sleep的原理

Thread.java

首先还是从java入手,查看sleep方法,可以发现它直接就是一个native方法:

public static native void sleep(long millis) throws InterruptedException;

为了查看native方法的具体逻辑,我们就需要下载openjdk和hotspot的源码了,下载地址:http://hg.openjdk.java.net/jdk8

查看Thread.c:jdk源码目录src/java.base/share/native/libjava

可以看到对应的jvm方法是JVM_Sleep:

static JNINativeMethod methods[] = {
   
    ...
    {
   "sleep",            "(J)V",       (void *)&JVM_Sleep},
    ...
};

查看jvm.cpp,hotspot目录src/share/vm/prims

找到JVM_Sleep方法,我们关注其重点逻辑:

方法的逻辑中,首先会做2个校验,分别是睡眠时间和线程的打断标记。其实这2个数据的校验都是可以放到java层,不过jvm的设计者将其放到了jvm的逻辑中去判断。

如果睡眠的时间为0,那么会调用系统级别的睡眠方法os::sleep(),睡眠时间为最小时间间隔。在睡眠之前会保存线程当前的状态,并将其设置为SLEEPING。在睡眠结束之后恢复线程状态。

接着就是sleep方法的重点,如果睡眠时间不为0,同样需要保存和恢复线程的状态,并调用系统级别的睡眠方法os::sleep()。当然睡眠的时间会变成指定的毫秒数。

最重要的区别是,此时会判断os::sleep()的返回值,如果是打断状态,那么就会抛出一个InterruptException!这里其实就是InterruptException产生的源头

JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
  JVMWrapper("JVM_Sleep");
	//如果睡眠的时间小于0,则抛出异常。这里数据的校验在jvm层逻辑中校验
  if (millis < 0) {
   
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
  //如果线程已经被打断了,那么也抛出异常
  if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
   
    THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
  }
  ...
  //这里允许睡眠时间为0
  if (millis == 0) {
   
    ...{
   
      //获取并保存线程的旧状态
      ThreadState old_state = thread->osthread()->get_state();
      //将线程的状态设置为SLEEPING
      thread->osthread()->set_state(SLEEPING);
      //调用系统级别的sleep方法,此时只会睡眠最小时间间隔
      os::sleep(thread, MinSleepInterval, false);
      //恢复线程的状态
      thread->osthread()->set_state(old_state);
    }
  } else {
   
    //获取并保存线程的旧状态
    ThreadState old_state = thread->osthread()->get_state();
    //将线程的状态设置为SLEEPING
    thread->osthread()->set_state(SLEEPING);
    //睡眠指定的毫秒数,并判断返回值
    if (os::sleep(thread, millis, true) == OS_INTRPT) {
   
        ...
        //抛出InterruptedException异常
        THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
    }
    //恢复线程的状态
    thread->osthread()->set_state(old_state);
  }
JVM_END

查看os_posix.cpp,hotspot目录src/os/posix/vm

我们接着查看os::sleep()方法:

首先获取线程的SleepEvent对象,这个是线程睡眠的关键

根据是否允许打断分为2个大分支,其中逻辑大部分是相同的,区别在于允许打断的分支中会在循环中额外判断打断标记,如果打断标记为true,则返回打断状态,并在外层方法中抛出InterruptedException

最终线程睡眠是调用SleepEvent对象的park方法完成的,该对象内部的原理后面统一说

int os::sleep(Thread* thread, jlong millis, bool interruptible) {
   
  //获取thread中的_SleepEvent对象
  ParkEvent * const slp = thread->_SleepEvent ;
  ...
  //如果是允许被打断
  if (interruptible) {
   
    //记录下当前时间戳,这是时间比较的基准
    jlong prevtime = javaTimeNanos
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值