join例子
join就是某个线程加入的意思,或者说,插队,可以保证线程的顺序执行。我们看一个简单的例子:
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}, "A");
t1.start();
//t1.join进来之后就要等待t1的死亡才能执行main线程
t1.join();
System.out.println("main end");
}
}
结果一定是:
main start
A
main end
为什么main线程要t1结束才执行呢?这里面是如何实现的呢?
我们看一下join的源码:
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) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
假设millis=0
,那么核心的代码就是:
while (isAlive()) {
wait(0);
}
如果加上this
这个语法糖,那么这个代码其实就是:
while (this.isAlive()) {
this.wait(0);
}
因为谁调用的谁就是this
,所以这里的代码其实就是:
while (t1.isAlive()) {
t1.wait(0);
}
t1.wait()
并非t1
线程进入等待状态,我们看到整个join方法其实是由synchronized
修饰的,那么其实就可以写成
synchronized (this)
或者
synchronized (t1)
想进入这个方法的线程首先要获取t1
对象的锁,然后,该线程执行等待。
等待是Thread
的一种状态,它必须和notify()
或者notifyAll()
结合使用。我们可以先看下源码中Thread
的几种状态(因为下面我们会打印这些状态):
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
解析
为了更好地理解join
,我们把
while (isAlive()) {
wait(0);
}
搬到外面来,然后也给方法加上锁:
public class ThreadJoinDemo extends Thread {
static ThreadJoinDemo subThread;
static Thread mainThread;
public void run() {
synchronized (subThread){
assert Thread.holdsLock(subThread);
System.out.println(Thread.currentThread().getName() + " acquired a lock on subThread");
System.out.println(Thread.currentThread().getName() + " is working");
System.out.println(Thread.currentThread().getName() + " state:" + Thread.currentThread().getState());
System.out.println("main state:" + mainThread.getState());
}
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] ar) throws Exception {
subThread = new ThreadJoinDemo();
subThread.setName("subThread");
subThread.start();
synchronized (subThread) {
assert Thread.holdsLock(subThread);
System.out.println(Thread.currentThread().getName() + " acquired a lock on subThread");
mainThread = Thread.currentThread();
Thread.sleep(1000);
while (subThread.isAlive()) {
subThread.wait(0);
}
}
System.out.println("after join, main state: " + mainThread.getState());
System.out.println(Thread.currentThread().getName() + " completed");
}
}
打印地结果是:
main acquired a lock on subThread
subThread acquired a lock on subThread
subThread is working
subThread state:RUNNABLE
main state:WAITING
after join, main state: RUNNABLE
main completed
我们可以看到main线程获取了subThread
的对象锁,假设没有subThread.wait(0);
,也就是说main线程不进入waiting set,不出让锁标记位,那么ThreadJoinDemo
的run
方法将永远不会执行,因为run
也要抢占subThread
的锁标记。
当main线程出让锁标记位后,他就进入到等待队列中等着被notify
。
所以我们在run
方法中看到的main线程的状态是WAITING
。
当
synchronized (subThread) {
assert Thread.holdsLock(subThread);
System.out.println(Thread.currentThread().getName() + " acquired a lock on subThread");
mainThread = Thread.currentThread();
Thread.sleep(1000);
while (subThread.isAlive()) {
subThread.wait(0);
}
}
执行完后,main线程的状态又变成了RUNNABLE
。从WAITING
到RUNNABLE
的转换,那main到底是被谁唤醒的呢?
我们看一下join的javadoc:
线程结束后notifyAll
会调用,所以当run
方法走完,main线程就被唤醒了,然后可以继续执行了。