线程的合并的含义就是 将几个并行线程的线程合并为一个单线程执行,应用场景是 当一个线程必须等待另外一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,它不是静态方法。bash
join有3个重载的方法:并发
void join():当前线程等该加入该线程后面,等待该线程终止。
void join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 若是在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,从新等待cpu调度。
void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。若是在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,从新等待cpu调度。
ide
参考:猿码道:啃碎并发(二)post
新建一个Thread类,重写run()方法:测试
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("子线程执行完毕");
}
}
复制代码
新建测试类,测试Join()方法:ui
public class TestThread {
public static void main(String[] args) {
//循环五次
for (int i = 0; i < 5; i++) {
MyThread thread = new MyThread();
//启动线程
thread.start();
try {
//调用join()方法
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行完毕");
System.out.println("~~~~~~~~~~~~~~~");
}
}
}
复制代码
输出结果以下:this
子线程执行完毕
主线程执行完毕
~~~~~~~~~~~~~~~
子线程执行完毕
主线程执行完毕
~~~~~~~~~~~~~~~
子线程执行完毕
主线程执行完毕
~~~~~~~~~~~~~~~
子线程执行完毕
主线程执行完毕
~~~~~~~~~~~~~~~
子线程执行完毕
主线程执行完毕
~~~~~~~~~~~~~~~
复制代码
结果分析: 子线程每次都在主线程以前执行完毕,即子线程会在主线程以前执行。spa
代码中循环5次是为了排除线程执行的随机性,若不能说明问题,能够调大循环次数进行测试。
操作系统
查看Thread类源码:线程
public final void join() throws InterruptedException {
//当调用join()时,实际是调用join(long)方法
join(0);
}
复制代码
查看Join(long)方法源码:
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) { //因为上一步传入参数为0,所以调用当前判断
while (isAlive()) { //判断子线程是否存活
wait(0); //调用wait(0)方法
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
复制代码
查看isAlive()方法源码:
/**
* Tests if this thread is alive. A thread is alive if it has
* been started and has not yet died.
* 测试线程是否还活着。若是线程存活的话它就是已经开始,尚未死亡的状态。
* @return <code>true</code> if this thread is alive;
* <code>false</code> otherwise.
*/****
public final native boolean isAlive();
复制代码
说明: 该方法为本地方法,判断线程对象是否存活,若线程对象调用了start()方法,在没有死亡的状况下此判断为true。在上述例子中,因为调用了子线程的start()方法,而且没有结束操做,所以判断true。
查看wait()方法源码:
public final native void wait(long timeout) throws InterruptedException;
复制代码
说明: 该方法为本地方法,调用此方法的当前线程须要释放锁,并等待唤醒。在上述例子中,主线程调用子线程对象的join()方法,所以主线程在此位置须要释放锁,并进行等待。
wait()与wait(0)的区别:
查看wait()方法源码,wait()方法只调用了wait(0),以下:
public final void wait() throws InterruptedException {
wait(0);
}
所以,wait()与wait(0)相同。
复制代码
咱们来撸一撸上述步骤: 主线程wait()等待,子线程调用了run()执行,打印“子线程执行完毕”。此时,主线程还没被唤醒,尚未执行下面的操做。那么,问题来了,谁?在何时?唤醒了主线程呢?
查看Thread类中存在exit()方法,源码以下:
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
* 这个方法由系统调用,当该线程彻底退出前给它一个机会去释放空间。
*/
private void exit() {
if (group != null) { //线程组在Thread初始化时建立,存有建立的子线程
group.threadTerminated(this); //调用threadTerminated()方法
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
复制代码
经过debug,exit()在线程执行完run()方法以后会被调用,此时线程组中存在当前子线程,所以会调用线程组的threadTerminated()方法。 查看ThreadGroup.threadTerminated()方法源码:
/** Notifies the group that the thread {@code t} has terminated.
* 通知线程组,t线程已经终止。
*
void threadTerminated(Thread t) {
synchronized (this) {
remove(t); //从线程组中删除此线程
if (nthreads == 0) { //当线程组中线程数为0时
notifyAll(); //唤醒全部待定中的线程
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
复制代码
经过此方法,将子线程从线程组中删除,并唤醒其余等待的线程。在上述例子中,此时子线程被销毁,并释放占用的资源,并唤醒等待中的线程。而在join()方法中等待的主线程被唤醒,并得到锁,打印“主线程执行完毕”。
Join()方法,使调用此方法的线程wait()(在例子中是main线程),直到调用此方法的线程对象(在例子中是MyThread对象)所在的线程(在例子中是子线程)执行完毕后被唤醒。
因为线程的启动与销毁实际上是由操做系统进行操做,因此在描述的时候刻意略去,若是有疑惑的地方,能够查看C++编写的本地方法。
复制代码