我的原则:先会用再说,内部慢慢来
Thread.join
一、 作用
- join 的作用是啥:让出当前锁住的对象,让别的线程跑完再来弄我的线程。
二、 注意点
- 记住锁是锁对象,不是锁住线程
- Join 方法是 Thread对象的,wait方法是 Object 对象的,Join的底层实现是 wait
三、 模拟 join 方法 (入门)
锁住的是指定的 demo 对象
/**
* @author jeb_lin
* 下午2:20 2023/4/12
*/
public class JoinTestDemo {
public static void main(String[] args) throws InterruptedException {
// 被锁住的对象
Object lockObj = new Object();
JoinThreadDemo demo = new JoinThreadDemo(lockObj);
Thread threadA = new Thread(demo, "thread-ok1");
threadA.start();
synchronized (lockObj) {
System.out.println("Main—— synchronized ... ");
if (threadA.isAlive()) {
/*
lockObj 的对象监视器 ObjectMonitor,释放掉当前的线程(现在是Main线程),
放到 waitSet 里面去
*/
lockObj.wait();
}
}
System.out.println("Main—— End...");
}
}
class JoinThreadDemo implements Runnable {
private Object lockObj;
public JoinThreadDemo(Object lockObj) {
this.lockObj = lockObj;
}
public void run() {
System.out.printf("%s enter run...\n", Thread.currentThread().getName());
try {
// 休眠一秒,让Main线程先拿到锁
TimeUnit.SECONDS.sleep(1);
synchronized (this.lockObj) {
System.out.printf("%s synchronized begins \n", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
System.out.printf("%s has synchronized finished \n", Thread.currentThread().getName());
/*
锁头释放,也就是 ObjectMonitor 唤醒 _waitSet 队列中的线程,喊他们起床抢锁
*/
this.lockObj.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 控制台输出
Main—— synchronized ...
thread-ok1 enter run...
thread-ok1 synchronized begins
thread-ok1 has synchronized finished
Main—— End...
- 解析
上述Main线程与threadA俩线程共享资源是 lockObj 对象,那么synchronzied 锁住的对象也就是 lockObj 对象,lockObj 对象的监视器盯着哪个线程 Thread,哪个线程 Thread 就能继续往下走。
于是乎,看上述控制台输出,Main线程执行到 synchronzied(demo) 内部的时候,调用 wait 方法,也就是让目前 lockObj 对象的 ObjectMonitor 释放当前 _owner 中的 Thread,让其他线程抢到锁,然后执行。thread-ok1 被lockObj 对象的 ObjectMonitor盯上后,执行代码完毕,调用 notifyAll,把刚刚 wait 的Main线程给唤醒,于是 Main 继续往下走。
四、 实战 join 方法 (实战)
锁住的是线程对象。
- 代码
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("Thread1—— starts");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread1—— ends");
},"t1");
Thread t2 = new Thread(() -> {
try {
t1.join(); // t2等待t1执行完毕才会往下走
// 等同于下面
// synchronized (t1){
// while (t1.isAlive()) {
// t1.wait(0);
// }
// }
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread2 starts");
System.out.println("Thread2 ends");
},"t2");
t1.start();
t2.start();
}
- 输出 ( 等Thread1,Thread2 全部跑完,再跑Main线程)
Thread1—— starts
Thread1—— ends
Thread2 starts
Thread2 ends![在这里插入图片描述](https://img-blog.csdnimg.cn/d2ff3687ee6c4afe9d948fe2d9d5f1a1.png)
五、 join 源码剖析 (剖析)
Thread.java
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) {
// 如果当前线程处于running状态,那么就无限等待
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
// 需要wait的时长 = 最长等待时长 - 已经等待时长
long delay = millis - now;
if (delay <= 0) {
break;
}
// 继续让出 cpu ,释放对象的锁,进入阻塞状态。
wait(delay);
// 已经等待多少ms了
now = System.currentTimeMillis() - base;
}
}
}
---
Object.java
/**
* Causes the current thread to wait until either another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or a
* specified amount of time has elapsed.
* 等到外部 notify 或者 timeout时间到,重新去抢夺锁。
* ...
* * <li>The specified amount of real time has elapsed, more or less. If
* {@code timeout} is zero, however, then real time is not taken into
* consideration and the thread simply waits until notified.
* timeout = 0 表示没超时一说,只能被notify
* /
*
public final native void wait(long timeout) throws InterruptedException;
// wait方法,释放锁,让出cpu。
六、 实战2的代码思考。
join的底层是方法: public final synchronized void join(long millis)
该方法是实例方法,那么 synchronized 锁的的是对象,锁住的是 this,this是什么,直接看debug。
显示的是 Thread[t1,5,main] ,也就是 t1线程对象本身。 ( thread2.join(); 同理)
![debug](https://img-blog.csdnimg.cn/20190828204801926.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1N3b3JkNTI4ODg=,size_16,color_FFFFFF,t_70
七、 重点
于是乎与参考最上面的 (1. 模拟 join 方法)理解,这样子就理解了。
thread1.join();
等同于 ---->
synchronized (t1){
while (t1.isAlive()) {
t1.wait(0);
}
}
也就是 t2 线程,释放锁对象 t1 .
t1线程,作为一个抢占资源,他的ObjectMonitor目前是盯着t2的,然后此时t2线程调用 t1.wait()方法,表明 t1这个抢占资源的ObjectMonitor把 owner中的 t2 放到waitSet里面去,让其他线程有机会跑,此时的其他线程就是t1
还有个细节,thread1的 run方法跑到最后,肯定会唤醒Main线程,具体看下JVM源码
// 位于/hotspot/src/share/vm/runtime/thread.cpp中
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
// ...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
// 看这里!!!
ensure_join(this);
// ...
}
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);
// 看这里!!!
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}