java thread.await_Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解

前言

线程并发系列文章:

前面几篇文章深入分析了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 原理

6、总结

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

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

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

66a24839c19b

image.png

此处需要说明的是:

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

下篇分析ReentrantLock、ReentrantReadWriteLock 原理及其应用。

本文基于jdk1.8。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值