线程的合并的含义就是 将几个并行线程的线程合并为一个单线程执行 ,应用场景是 当一个线程必须等待另一个线程执行完毕才能执行时 ,Thread类提供了join方法来完成这个功能,注意,它 不是静态方法 。
join有3个重载的方法:
void join():当前线程等该加入该线程后面,等待该线程终止。
void join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
参考: juejin.im/post/5a72d4…
新建一个Thread类,重写run()方法:
public class MyThread extends Thread { @Override public void run() { System.out.println("子线程执行完毕"); } }
新建测试类,测试Join()方法:
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("~~~~~~~~~~~~~~~"); } } }
输出结果如下:
子线程执行完毕 主线程执行完毕 ~~~~~~~~~~~~~~~ 子线程执行完毕 主线程执行完毕 ~~~~~~~~~~~~~~~ 子线程执行完毕 主线程执行完毕 ~~~~~~~~~~~~~~~ 子线程执行完毕 主线程执行完毕 ~~~~~~~~~~~~~~~ 子线程执行完毕 主线程执行完毕 ~~~~~~~~~~~~~~~
结果分析:子线程每次都在主线程之前执行完毕,即子线程会在主线程之前执行。
代码中循环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++编写的本地方法。