Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解

前言

线程并发系列文章:

Java 线程基础
Java 线程状态
Java “优雅”地中断线程-实践篇
Java “优雅”地中断线程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有误
Java Unsafe/CAS/LockSupport 应用与原理
Java 并发"锁"的本质(一步步实现锁)
Java Synchronized实现互斥之应用与源码初探
Java 对象头分析与使用(Synchronized相关)
Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
Java Synchronized 重量级锁原理深入剖析上(互斥篇)
Java Synchronized 重量级锁原理深入剖析下(同步篇)
Java并发之 AQS 深入解析(上)
Java并发之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
Java 并发之 ReentrantLock 深入分析(与Synchronized区别)
Java 并发之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)
最详细的图文解析Java各种锁(终极篇)
线程池必懂系列

前面几篇文章深入分析了Thread、synchronized、AQS等相关知识,基础打好了,接下来就来分析常见的几个方法的应用、原理及其容易混淆的地方。
通过本篇文章,你将了解到:

1、Thread.sleep 应用及原理
2、Thread.yield 应用及原理
3、Thread.join 应用及原理
4、Object.wait 应用及原理
5、Condition.await 应用及原理
6、总结

1、Thread.sleep 应用及原理

Thread.sleep 应用

Thread.sleep 作用:

调用 Thread.sleep后,线程进入休眠状态并让出CPU,等待超时时间耗尽,再次被CPU 调度运行。

先来看看Thread.java 里的定义:

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

可以看出,其接受一个毫秒为单位的超时时间,并可能抛出InterruptedException 异常。当然也可以通过:

 public static void sleep(long millis, int nanos)
    throws InterruptedException{...}

追加纳秒时间。
来看看Demo:

    private void testSleep() {
        try {
            Thread.sleep(-1);//----->(1)
            Thread.sleep(0);//------>(2)
            Thread.sleep(1);//------>(3)
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

上述代码标注的(1)/(2)/(3),分别传入负数、0、正数,结果分别是什么呢?
(1)
会抛出IllegalArgumentException 异常。
(2)
正常运行。
(3)
休眠1ms后线程得以继续执行。

Thread.sleep 原理

接着从源码的角度查看Thread.sleep(xx)接收不同参数以及为什么可以响应中断。
Thread.sleep(xx)为native方法,先找到其对应的jni函数映射:

#Thread.c
static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

再找到C++函数实现:

#jvm.cpp
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
  JVMWrapper("JVM_Sleep");

  if (millis < 0) {
    //如果传入的时间为负数,则抛出IllegalArgumentException 异常
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }

  //如果发生中断,则将中断标记位置为false,然后抛出InterruptedException 异常
  if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
    THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
  }
  ...
  if (millis == 0) {
    //传入的时间为0
    //ConvertSleepToYield 表示是否将sleep转为yield,在x86下是true
    if (ConvertSleepToYield) {
      //调用系统的yield()函数
      os::yield();
    } else {
      ...
      //调用系统的sleep()函数,并指定最小的时间MinSleepInterval(1ms)
      os::sleep(thread, MinSleepInterval, false);
      ...
    }
  } else {
    //传入的时间>0
    if (os::sleep(thread, millis, true) == OS_INTRPT) {
        //睡眠醒过来后,发现发生了中断,抛出中断异常
        THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
      }
    }
    thread->osthread()->set_state(old_state);
  }
  if (event.should_commit()) {
    event.set_time(millis);
    event.commit();
  }
#ifndef USDT2
JVM_END

分析到此处,上面的疑惑基本解开了。

1、Thread.sleep(-1),传参是负数,则抛出异常。
2、Thread.sleep(0),传参是0,则调用Thread.yield()。
3、不是1、2两点的值,则调用ParkEvent.park(xx)-->os::Linux::safe_cond_timedwait()-->NPTL.pthread_cond_timedwait(xx) 按照限时时间挂起线程。
4、Thread.sleep(xx)过程中,若是发生中断,则抛出中断异常,并将中断标记位置为false。

2、Thread.yield 应用及原理

Thread.yield 应用

Thread.yield 作用:

调用Thread.yield后,线程让出CPU,CPU调度优先级比当前线程更高或者优先级相等的线程,若没有则Thread.yield调用立刻返回。
在并发场景下,可以提高CPU利用率。

先来看看Thread.java 里的定义:

public static native void yield();

Thread.yield 虽然平时很少用到,但不代表没用,回顾之前分析AQS源码时判断节点是否在同步同队列里时的做法:

#AbstractQueuedSynchronizer.java
    final boolean transferAfterCancelledWait(Node node) {
        ...
        //不断检测是否在同步队列里
        while (!isOnSyncQueue(node))
            //没有无休止地轮训,而是yield一会
            Thread.yield();
        return false;
    }

比如:线程A往同步队列里放节点,线程B查询,因为A、B并不是严格意义上的同步关系,因此无需用等待/通知机制,B知道A放节点速度很快,因此B仅仅只需要Thread.yield()简单让出CPU即可。

Thread.yield 原理

#jvm.cpp
JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass))
  JVMWrapper("JVM_Yield");
  //不支持yield,直接返回
  //默认是支持的
  if (os::dont_yield()) return;
  //ConvertYieldToSleep 默认false
  if (ConvertYieldToSleep) {
    os::sleep(thread, MinSleepInterval, false);
  } else {
    //调用系统yield
    os::yield();
  }
JVM_END
#os_linux.cpp
void os::yield() {
  //系统调度
  sched_yield();
}

可以看出,Thread.yield 不响应中断。

3、Thread.join 应用及原理

Thread.join 应用

Thread.join 作用:

调用Thread.join后,调用者线程阻塞等待直到目标线程停止运行后再返回。
可以用来做线程间简单同步,比如A线程(调用者线程)等待B(目标线程)执行结束后再做其它动作。

在Thread.java 里的定义:

public final void join() throws InterruptedException(){...}

当然也可以设置超时时间,若是超时时间耗尽后,目标线程还没执行完毕Thread.join也是可以提前返回的。

//超时单位毫秒
public final synchronized void join(long millis){...}

来看看Demo:

public class TestThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 执行到末尾了");
            }
        });
        t1.start();
        System.out.println("等待 t1 执行完毕");
        t1.join();
        System.out.println("t1 执行结束了");
    }
}

主线程调用Thread.join等待t1执行完毕。

Thread.join 原理

#Thread.java
    public final void join() throws InterruptedException {
        //超时时间0
        join(0);
    }

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            //超时时间必须>=0
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                //此处的wait就是this.wait(0),也就是thread.wait()
                //一直等待,直到收到notify
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    //超时过了,直接返回
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

可以看出,Thread.join借助Object.wait/Object.notify 来实现线程间同步功能的。而且Thread.join 没有显式处理中断的逻辑,但却声明需要抛出中断异常,实际上中断异常是Object.wait抛出的。
既然有Thread.wait在等待,那么想要Thread.wait返回,那么需要调用Thread.notify,这个在哪调用的呢?只能是线程结束时调用。

#thread.cpp
//线程结束,调用该方法
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  ...
  ensure_join(this);
  ...
}

static void ensure_join(JavaThread* thread) {
  ...
  ObjectLocker lock(threadObj, thread);
  ...
  //唤醒等待在thread对象上的线程
  lock.notify_all(thread);
  ...
}

#ObjectSynchronizer.hpp
void notify_all(TRAPS)      { ObjectSynchronizer::notifyall(_obj,    CHECK); }

可以看出,线程结束后最终调用了thread.notify_all唤醒所有等待它执行完成的线程。
因为Thread.join里调用Object.wait,而Object.wait需要抛出中断异常,因此Thread.join也需要抛出中断异常。

4、Object.wait 应用及原理

Object.wait 应用

Object.wait 作用:

调用Object.wait 后,线程被阻塞,等待其它线程唤醒它。
用来做线程间的同步。

来看看Object.java里的定义:

public final void wait() throws InterruptedException

可以看出该方法可能会抛出中断异常。
当然wait(xx)可以设置超时时间。

public final native void wait(long timeout) throws InterruptedException;

超时时间单位为毫秒(ms)。

看看Demo:

public class TestThread {
    public static Object object = new Object();
    public static volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    try {
                        System.out.println("flag is:" + flag + " t1 wait");
                        while (!flag)
                            object.wait();
                        System.out.println("flag is:" + flag + " t1 continue");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t1.start();

        Thread.sleep(2000);
        synchronized (object) {
            System.out.println("flag is:" + flag + " main change flag");
            flag = true;
            object.notify();
            System.out.println("flag is:" + flag + " main notify t1");
        }
    }
}

t1发现flag==false,于是调用wait等待flag变为true,主线程修改flag为true,调用notify唤醒t1,这就完成了一次简单的线程间通信(同步)。

Object.wait 原理

Object.wait 源码在之前的Java Synchronized 重量级锁原理深入剖析下(同步篇)已经分析过,本次着重分析它是如何响应中断的。

#ObjectMonitor.cpp
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
   //若是发生了中断,则抛出中断异常
   if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
     ...
     THROW(vmSymbols::java_lang_InterruptedException());
     return ;
   }
   ...
   //释放锁
   exit (true, Self) ; 
   { 
     {
       if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
          //中断
       } else
       if (node._notified == 0) {
         //挂起线程,阻塞于此
         if (millis <= 0) {
            Self->_ParkEvent->park () ;
         } else {
            ret = Self->_ParkEvent->park (millis) ;
         }
       }
     }
   }
   //唤醒之后,先获取锁
      if (v == ObjectWaiter::TS_RUN) {
         ...
     } else {
         //重新获取锁
         ReenterI (Self, &node) ;
         node.wait_reenter_end(this);
     }

   if (!WasNotified) {
     if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
       //唤醒后,发现曾经发生过中断,于是抛出异常
       THROW(vmSymbols::java_lang_InterruptedException());
     }
   }
}

由此可见,调用Object.wait()后:

1、线程释放锁。
2、线程调用ParkEvent.park(xx)-->os::Linux::safe_cond_timedwait()-->NPTL.pthread_cond_timedwait(xx) 挂起线程。
3、线程被唤醒后继续争抢锁。
4、若在1、2、3 阶段发生中断,则重置中断标记位,并抛出中断异常。

5、Condition.await 应用及原理

Condition.await 应用

Condition.await 作用:
与Object.wait 作用一样。

看看Condition.java里的定义:

void await() throws InterruptedException;

可以看出,可能会抛出中断异常。
当然await(xx)可以设置超时时间。

boolean await(long time, TimeUnit unit) throws InterruptedException;

超时时间单位可选毫秒/纳秒等。

看看Demo:

public class TestThread {
    public static Lock myLock = new ReentrantLock();
    public static Condition condition = myLock.newCondition();
    public static volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                myLock.lock();
                try {
                    System.out.println("flag is:" + flag + " t1 await");
                    while (!flag)
                        condition.await();
                    System.out.println("flag is:" + flag + " t1 continue");
                } catch (InterruptedException e) {
                } finally {
                    myLock.unlock();
                }
            }
        });
        t1.start();

        Thread.sleep(2000);
        myLock.lock();
        try {
            System.out.println("flag is:" + flag + " main change flag");
            flag = true;
            condition.signal();
            System.out.println("flag is:" + flag + " main signal t1");
        } catch (Exception e) {
        } finally {
            myLock.unlock();
        }
    }
}

与Object.wait Demo类似,只是Object.wait需要配合synchronized使用,而Condition.await需要配合Lock使用。

Condition.await 原理

原理请移步:Java并发之 AQS 深入解析(下)

6、总结

Thread.sleep/Thread.join/Thread.yield 和锁没有任何关系,而Object.wait调用前需要先获取synchronized锁,Condition.await调用前需要先获取Lock锁,因此它们和锁有关系。

之所以容易把它们几个弄混,是因为表面上看调用这些方法后都会使得线程阻塞。除去Thread.yield外,其它方法阻塞线程都是通过调用底层的NPTL对应的函数。

最后,用一张图总结本篇分析的内容:


image.png

此处需要说明的是:

我们关注与锁有无关系是基于外部上锁,临界区执行Thread.sleep/Thread.yield/Thread.join/Object.wait/Condition.await 等方法是否与锁有关系。因此Thread.join 是与锁没有关系,只是内部使用了synchronized+Object.wait。

下篇分析ReentrantLock、ReentrantReadWriteLock 原理及其应用。
本文基于jdk1.8。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Java/Android

你可能感兴趣的:(Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解)