前言
线程并发系列文章:
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对应的函数。
最后,用一张图总结本篇分析的内容:
此处需要说明的是:
我们关注与锁有无关系是基于外部上锁,临界区执行Thread.sleep/Thread.yield/Thread.join/Object.wait/Condition.await 等方法是否与锁有关系。因此Thread.join 是与锁没有关系,只是内部使用了synchronized+Object.wait。
下篇分析ReentrantLock、ReentrantReadWriteLock 原理及其应用。
本文基于jdk1.8。
您若喜欢,请点赞、关注,您的鼓励是我前进的动力
持续更新中,和我一起步步为营系统、深入学习Android/Java
1、Android各种Context的前世今生
2、Android DecorView 一窥全貌(上)
3、Android DecorView 一窥全貌(下)
4、Window/WindowManager 不可不知之事
5、View Measure/Layout/Draw 真明白了
6、Android事件分发全套服务
7、Android invalidate/postInvalidate/requestLayout 彻底厘清
8、Android Window 如何确定大小/onMeasure()多次执行原因
9、Android事件驱动Handler-Message-Looper解析
10、Android 键盘一招搞定
11、Android 各种坐标彻底明了
12、Android Activity/Window/View 的background
13、Android IPC 之Service 还可以这么理解
14、Android IPC 之Binder基础
15、Android IPC 之Binder应用
16、Android IPC 之AIDL应用(上)
17、Android IPC 之AIDL应用(下)
18、Android IPC 之Messenger 原理及应用
19、Android IPC 之获取服务(IBinder)
20、Android 存储基础
21、Android 10、11 存储完全适配(上)
22、Android 10、11 存储完全适配(下)
23、Java 并发系列不再疑惑