前言
线程并发系列文章:
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。
网友评论