1、案例需求
现有a,b,c三个线程,要求b,c线程必须确保在a执行完成后执行(这里要求用join来完成)
2、案例代码
package com.rcb.edg;
/**
* @author : jizhuang.wang
* @version V1.0
* @Project: hive-udf
* @Package com.rcb.edg
* @Description: TODO
* @date Date : 20220508 12:56
*/
public class SyncTest {
//此次synchronized锁定的是SyncTest类的实列对象this,而非class类
public final synchronized void a(int name) {
System.out.println("-------开始-------"+name);
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("--结束--"+name);
}
//此次synchronized锁定的是SyncTest.class类,而非对象实例
public static synchronized void b(int name) {
System.out.println("-------开始-------"+name);
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("--结束--"+name);
}
public static void main(String[] args) throws InterruptedException {
SyncTest o = new SyncTest();
// new Thread(()->{o.b(3);}).start();
// new Thread(()->{o.b(4);}).start();
Thread a = new Thread(() -> new SyncTest().a(1));
Thread b = new Thread(() -> {
try {
//在b线程实例中调用a线程对象实例的join方法,以达到a线程执行完成后在执行b的效果
//思考下:为什么能确保,执行完a后在执行b这种顺序关系呢?a->b
//要知道在当前b线程对象中调用a线程对象的join方法,而a对象的join方法是有synchronized修饰关键字锁的,
//那么就知道这里其实是获取锁了。锁对象就是调用了join方法的那个对象(这里被锁定的对象就是a线程的对象实例)
//b线程实例获取锁资源后,当前线程条件允许下调用wait()方法,让当前线程(b线程实例)释放锁并挂起线程 即:wait释放了锁后被阻塞
//补充说明:
//1.synchronized某个实例方法:主要是获取当前运行对象的Monitor
//2.synchronized某个静态方法:主要是获取类对象的Monitor
//3.synchronized方法块:主要获取同步块中对象的Monitor
//4.获取锁就是获取被锁对象Monitor的_owner所有者权限
//5.当线程a执行完毕后,jvm在关闭线程之前会检测线阻塞在a线程对象上的线程,然后执行notfyAll(),这样b线程就被唤醒了
a.join();
new SyncTest().a(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread c = new Thread(() -> {
try {
a.join();//在c线程中调用a.join方法,达到a执行完成后在执行c
new SyncTest().a(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
c.start();
b.start();
a.start();
//此处为main线程(调用者)调用a线程(被调用者)的join方法:主线程main,必须在a线程执行完成后在输出
//但不保证b,c线程先与main线程执行完哦
a.join();
System.out.println("main_线程执行结束了");
}
}
3. join源码片段
/**
* 等待该线程终止的时间最长为millis毫秒,超时时间为0意味着要一直等下去
* @param millis 以毫秒为单位的等待时间
* @throws InterruptedException
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
//等待时长millis小于0时(负数),报异常
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//millis等于0时,若当前调用线程是存活的,则线程无限等待,直至被其他线程唤醒
/**
例如:在main主线程中调用了线程t1.join()方法,则这里当前线程就是指的main主线程,这里的isAlive()方法,就会校验main线程是否还存活,存活则继续阻塞,或阻塞时间到了从新运行;
主要:这里wait(0)的是当前的main主线程,t1.join()就是在当前线程中调用的
**/
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
4、思考
join执行完后,是如何唤醒阻塞线程的呢? 线程唤醒
我们自己使用都是wait和notify/notifyAll成对出现的。否则线程将无限期等待下去。如果等待线程还是一个非守护线程。那么就会导致程序不能正常结束
当线程t1执行完毕是,jvm在关闭线程之前会检测线阻塞在t1线程对象上的线程,然后执行notfyAll(),这样t2就没唤醒了 理解join必须要理解线程thread、synchronized同步锁的用法及他们的原理哦
|