开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
这一章节是紧跟上一章节的,对于join方法同样有三个问题:1.是否释放锁?2.是否对中断敏感 ?3.是否释放CPU?
我们先来看一下源码
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
1.join方法是否对中断敏感
这次我们为了方便理解,先从是否对中断敏感,可以看到这个方法抛出InterruptedException异常,说明是对中断敏感的,也就是说你可以在现场join过程中去打断它。
2是否释放cpu
public final synchronized void join(long millis)
throws InterruptedException {
...
}
我们先从源码进行解析,首先看到这个这个方法体是synchronized关键字修饰的,而且锁对象就是谁调用谁就是锁对象。说明为了防止并发调用的情况。
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
这几行比较简单,就是记录当前时间戳和设置一个记录经过多久时间的差值now(后面会讲解now)
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
如果当前传进来的形参millis是0则进入if方法,我们的join方法默认调用的就是wait(0),接下来看while判断,isAlive()方法表示当前线程是否存活,如果调用者的线程不存活了(线程执行完毕或者未开启),则直接跳出while循环;如果存活就继续等待,这里的wait(0)
这里面有两个隐藏的知识点:
我们先简单写一个线程的例子:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程结束");
}
});
t1.start();
t1.join();
System.out.println("主线程执行完毕");
}
1.join方法是线程对象才能调用的这个方法。源代码中这个wait是调用的Object的,但是这是父类。其实这个wait方法前边有一个隐含的意义: this.wait(),this指的就是例子中的t1,还记得上面说的在方法中加锁吗?锁对象就是这个t1,那么这句话的含义就是释放了t1这个锁对象,并且当前线程进入了等待中。
2.这里源代码中为什么用while判断呢,这里不得不说假唤醒(虚假唤醒),在一个线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这个线程仍然可以从挂起状态变为可以运行状态( 也就是被唤醒),这就是所谓的虚假唤醒。 为的就是防止虚假唤醒。
从这里我们也可以得知,当前线程是释放cpu的。
else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
这里的else中,首先判断是否存活,跟上面一样存活的线程往下执行。代码比较简单,大概意思就是算出我要等待的时间和和已经过了多久的时间差值,如果是负数,说明已经过去了预期的millis时间了,跳出循环。如果不是负数,则等待相应的差值时间后再继续计算已经过了多久,重复这几个步骤,完成预期时间等待。
3.是否释放锁
我们下面写一个简单的代码做例子:
public class JoinRelase {
static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 2; i++) {
Thread thread = new Thread(new SubThread(),"Daemon Thread!"+i);
thread.setName("thread-" + i);
thread.start();
Thread.sleep(100);
}
System.out.println("主线程执行完毕");
}
static class SubThread implements Runnable {
@SneakyThrows
@Override
public void run() {
System.out.println("未获取到锁"+Thread.currentThread().getName());
synchronized (object) {
System.out.println("获取到锁!!!ThreadName: " + Thread.currentThread().getName());
Thread.currentThread().join();
}
}
}
}
执行效果如下:
程序为什么不结束呢?
可以看到thread-1线程被卡住了,因为1线程一直未获取到object锁对象,还记得我们刚刚看的源码,线程0执行join时源码wait方法释放的是什么锁呢,在这个例子中释放的是调用对象,也就是Thread.currentThread()对象,也就是名字是thread-0 这个对象!!!并不释放object这个锁对象!
同理我们如果把加锁synchronized (object) 换成了线程一释放的锁 ,那么我们执行效果是什么样子呢? 可以先想一下,示例代码如下:
public class JoinRelase {
static Object object = new Object();
static Thread tempThread;
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 2; i++) {
Thread thread = new Thread(new SubThread(),"Daemon Thread!"+i);
thread.setName("thread-" + i);
thread.start();
tempThread=thread;
Thread.sleep(100);
}
System.out.println("主线程执行完毕");
}
static class SubThread implements Runnable {
@SneakyThrows
@Override
public void run() {
System.out.println("未获取到锁"+Thread.currentThread().getName());
synchronized (tempThread) {
System.out.println("获取到锁!!!ThreadName: " + Thread.currentThread().getName());
Thread.currentThread().join();
}
}
}
}
效果如下:
线程0和线程1都获取到了锁,因为线程0 wait方法释放的就是Thread.currentThread()锁。具体要看当前的锁对象是谁。如果是调用join方法的锁对象,则释放。
所以join方法是否释放锁,可以说是释放调用对象这个对象锁!