【线程】 Thread.join 内部原理 (一)

我的原则:先会用再说,内部慢慢来


Thread.join

一、 作用

  1. join 的作用是啥:让出当前锁住的对象,让别的线程跑完再来弄我的线程。

二、 注意点

  1. 记住锁是锁对象,不是锁住线程
  2. 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();
        }
    }
}

  1. 控制台输出
Main—— synchronized ... 
thread-ok1 enter run...
thread-ok1 synchronized begins 
thread-ok1 has synchronized finished 
Main—— End...
  1. 解析
    上述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 方法 (实战)

锁住的是线程对象。

  1. 代码
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();
    }
  1. 输出 ( 等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();
}

八、 番外篇

下一章节:【线程】Object.wait 内部原理(二)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值