【線程】 Thread.join 內部原理 (一)

我的原則:先會用再說,內部慢慢來


Thread.join

1. 作用

  1. join 的作用是啥:讓出當前鎖住的對象,讓別的線程跑完再來弄我的線程。

2. 注意點

  1. 記住鎖是鎖對象,不是鎖住線程
  2. Join 方法是 Thread對象的,wait方法是 Object 對象的,Join的底層實現是 wait

3. 模擬 join 方法 (入門)

鎖住的是指定的 demo 對象

public class JoinTestDemo {
    public static void main(String[] args) throws InterruptedException {
        JoinThreadDemo demo = new JoinThreadDemo();
        Thread threadA = new Thread(demo, "MyThread");
        threadA.start();
        System.out.println("Main Begin");
        System.out.println("-------------");
        synchronized (demo) {
            System.out.println("synchronzied ... " + demo);
            if (threadA.isAlive()) {
	            System.out.println("0000");
	            /*
	                main線程,讓出對象鎖 ObjectMonitor
	             */
	            demo.wait();
            	System.out.println("wake up Main thread ...");
	        }

        }
        System.out.println("--------------");
        System.out.println("Main End");
    }
}

class JoinThreadDemo implements Runnable {
    public void run() {
        System.out.printf("%s begins: %s\n", Thread.currentThread().getName(), new Date());
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("run ..." + this);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("%s has finished: %s\n", Thread.currentThread().getName(), new Date());
        synchronized (this) {
            /*
                鎖頭釋放
             */
            this.notifyAll();
        }
    }
}
  1. output
Main Begin
-------------
synchronzied ... indi.sword.util.basic.Thread.JoinThreadDemo@1a86f2f1
0000
MyThread begins: Wed Aug 28 18:06:53 CST 2019
run ...indi.sword.util.basic.Thread.JoinThreadDemo@1a86f2f1
MyThread has finished: Wed Aug 28 18:06:56 CST 2019
wake up Main thread ...
--------------
Main End
  1. 解析
    上述Main線程與threadA倆線程共享資源是demo對象,那麼synchronzied 鎖住的對象也就是 demo 對象,那麼誰擁有 demo對象,誰就能繼續往下走。
    於是乎,看上述output,Main線程執行到 synchronzied(demo) 內部的時候,調用 wait 方法,讓出了鎖住的demo對象,threadA獲取到 demo對象後,執行代碼完畢,調用 notifyAll,把剛剛 wait 的Main線程給喚醒,於是 Main 繼續往下走。

4. 實戰 join 方法 (實戰)

鎖住的是線程對象。

  1. 代碼
public class JoinTest implements Runnable {
    private String name;
    public JoinTest(String name) {
        this.name = name;
    }
    public void run() {
        System.out.printf("%s begins: %s\n", name, new Date());
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("%s has finished: %s\n", name, new Date());
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new JoinTest("One"),"One");
        Thread thread2 = new Thread(new JoinTest("Two"),"Two");
        thread1.start();
        thread2.start();
        try {
        	/*
                Waits for this thread to die. 阻塞main線程,等待跑完這個線程
                join(10)的話,表示main線程等你thread1運行10ms,不管你是否執行完畢,都跑Main相關的了,Main如果要搶		  佔鎖,那就搶去。
             */
            thread1.join(); 
            thread2.join(); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread is finished");
    }
}
  1. 輸出 ( 等Thread1,Thread2 全部跑完,再跑Main線程)
One begins: Wed Aug 28 17:46:00 CST 2019
Two begins: Wed Aug 28 17:46:00 CST 2019
One has finished: Wed Aug 28 17:46:04 CST 2019
Two has finished: Wed Aug 28 17:46:04 CST 2019
Main thread is finished

5. 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。

6. 實戰2的代碼思考。

join的底層是方法: public final synchronized void join(long millis)
該方法是實例方法,那麼 synchronized 鎖的的是對象,鎖住的是 this,this是什麼,直接看debug。
顯示的是 Thread[One,5,main] ,也就是thread1線程對象本身。 ( thread2.join(); 同理)

debug

7. 重點

於是乎與參考最上面的 (1. 模擬 join 方法)理解,這樣子就理解了。

thread1.join();
等同於 ---->
synchronized (thread1){
     while (thread1.isAlive()) {
         thread1.wait(0);
     }
 }
 也就是 Main 線程,釋放鎖對象 thread1 . 

還有個細節,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();
}

8. 番外篇

下一章節:【線程】Object.wait 內部原理(二)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章