我的原則:先會用再說,內部慢慢來
Thread.join
1. 作用
- join 的作用是啥:讓出當前鎖住的對象,讓別的線程跑完再來弄我的線程。
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();
}
}
}
- 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
- 解析
上述Main線程與threadA倆線程共享資源是demo對象,那麼synchronzied 鎖住的對象也就是 demo 對象,那麼誰擁有 demo對象,誰就能繼續往下走。
於是乎,看上述output,Main線程執行到 synchronzied(demo) 內部的時候,調用 wait 方法,讓出了鎖住的demo對象,threadA獲取到 demo對象後,執行代碼完畢,調用 notifyAll,把剛剛 wait 的Main線程給喚醒,於是 Main 繼續往下走。
4. 實戰 join 方法 (實戰)
鎖住的是線程對象。
- 代碼
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");
}
}
- 輸出 ( 等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(); 同理)
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();
}