对线程join()方法的理解

发布时间 2023-08-06 09:08:47作者: 风筝上的猫

java线程的join()方法的理解

thread.join() 把指定的线程加入到当前线程,可以将两个交替执行的线程和并为顺序执行的线程。简单说就是同步
  • 例1:比如在线程B中调用了线程A的 join 方法,直到线程A执行完毕后,才会继续执行线程B。
  • 例2:再比如我们做查询操作,总任务需要返回三个查询列表,那么主线程就需要等待这三个线程全部执行完并返回结果后才可以结束,这时就需要用到 join 方法。(具体例子可以看博客 “线程池怎么用(实例)“)
  • 例3:某些情况、主线程中启用了子线程,如果子线程需要大量的算法,需要运算的时间较长,主线程可能会在子线程结束前就结束,这个时候如果想等待子线程结束后再结束主线程,可以使用join()方法。
 
/**例1**/
t.join();   //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。
 
/**例3**/
public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1= new Thread(()->{
            try {
                Thread.sleep(9000);//子线程处理中
                System.out.println("子线程处理完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.start();
        thread1.join();
        System.out.println("主线程结束");
    }
}
 
wait(0)这个方法的作用是让当前线程等待一段时间,时间为0表示线程立即进入等待状态。通常情况下,我们会给wait()方法传递一个时间参数,表示让线程等待多少毫秒。但是如果我们传递0或者一个负数,那么线程就会立即进入等待状态,直到被其他线程唤醒。需要注意的是,wait()方法必须在synchronized块中调用。
 

join源码解析

看一段JDK提供的源码
0
  • Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever. 意思是等待一段时间直到这个线程死亡。超时0意味着永远等待。
  • 从源码中我们可以看出来join方法的实现是通过wait,wait是一个Object提供的方法。当main线程调用 t.join 时,主线程会获得线程对象的锁(wait意味着拿到该对象的锁),调用该对象的 wait(time) ,直到该对象唤醒main线程。比如退出。这就意味着main 线程调用 t.join时,必须能够拿到线程t对象的锁如果拿不到时无法wait的
 
这里注意等待线程死亡的意思,是指如果主线程调用 t.join(100) ,那么主线程就要等待 t 线程,等待时间为100ms。
举个例子:
public class JoinTest {
    public static void main(String[] args) {
        Thread t=new Thread(new RunnableImpl());
        t.start();
        try {
            t.join(1000);
            System.out.println("join结束了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class RunnableImpl implements Runnable{

    @Override
    public void run() {
        try {
            System.out.println("开始sleep");
            Thread.sleep(1000);
            System.out.println("结束sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
结果是:
开始sleep
结束sleep
join结束了
=====>说明当主线程调用t.joni(1000)时,主线程等待t线程1秒钟。
 
 
但多运行几次发现,也会出现不同的结果:
开始sleep
join结束了
结束sleep
=====>这是因为 t 线程在进入就绪状态后,没有立刻被CPU调度分配时间片,因为CPU时间片的分配使用的是非公平策略,并不会因为先进入就绪队列就先执行,所以主线程在等待t线程1秒钟后便继续执行了,导致这样的现象出现。
 
 
如果让主线程等待1秒钟,而 t线程sleep2秒钟,那么这个现象会更加明显
@Override
public void run() {
    try {
        System.out.println("开始sleep");
        //Thread.sleep(1000);
        Thread.sleep(2000);
        System.out.println("结束sleep");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
结果:
开始sleep
join结束了
结束sleep
=====>很明显主线程只会等待1秒钟,不会理会t线程什么时候结束,1秒钟后主线程就会接着执行。
 
 
我们记着源码注释中还提了一句话,超时0意味着永远等待。也就是说主线程调用 t.join(0)或 t.join()就意味着主线程必须等待t线程执行完毕才可以执行。
join() 和 join(0) 的意义一样。在源码中我们就能知道这一点。
 
 
接着解释join源码解释下的第二点 ” main 线程调用 t.join时,必须能够拿到线程t对象的锁
创造一个新线程去抢夺对象t的锁:
public class JoinTest {
    public static void main(String[] args) {
        long start=System.currentTimeMillis();
        Thread t=new Thread(new RunnableImpl());
        new MythreadTest(t).start();
        t.start();
        try {
            t.join(1000);
            System.out.println("join结束了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("时间:"+(System.currentTimeMillis()-start));
    }
}

class RunnableImpl implements Runnable{

    @Override
    public void run() {
        try {
            System.out.println("开始sleep");
            //Thread.sleep(1000);
            Thread.sleep(2000);
            System.out.println("结束sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MythreadTest extends Thread{

    Thread thread;

    public MythreadTest(Thread thread){
        this.thread=thread;
    }

    @Override
    public void run() {
        holdThreadLock();
    }

    public void holdThreadLock(){
        synchronized (thread){
            System.out.println("拿到对象锁");

        try {
            Thread.sleep(9000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
            System.out.println("释放对象锁");
        }
    }
}
结果:
拿到对象锁
开始sleep
结束sleep
等九秒
释放对象锁
join结束了
时间:9013
===============================
还可能出现的结果:
开始sleep
拿到对象锁
结束sleep
等九秒
释放对象锁
join结束了
时间:9016
==========原因同上================
 
        在main方法中 通过new ThreadTest(t).start(); 实例化ThreadTest 线程对象, 它在holdThreadLock()方法中,通过 synchronized (thread),获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000),等待一秒钟,它也必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000ms~10000ms