栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程:搞懂 wait() 和 join()的关系

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程:搞懂 wait() 和 join()的关系

多线程:搞懂 wait() 和 join()的关系



前置知识:线程状态
   public enum State {
        
        NEW,
     
        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;
    }
  1. NEW (初始): 新创建线程对象,但还没有调用 start() 方法
  2. RUNNABLE (运行): java 把 就绪 (ready) 和运行 (running) 一起归属于runnable。线程对象创建后,被其它的线程(如: main线程)调用start() 方法。该线程就处于就绪(ready)状态进入可运行线程池中,等待cpu线程调度。就绪状态的线程获得cpu时间片后变为 运行状态 (running)
  3. BLOCKED (阻塞) : 线程阻塞于锁
  4. WAITING(等待) : 线程等待其它线程唤醒
  5. TIMED_WAITING (超时等待) : 该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. TERMINATED (终止) :该线程执行完毕



wait() 详解

wait() notify() notifyAll() 都是 Object类的方法,使用这三个方法的前提是先获得该对象的锁。

  1. 调用 wait 方法后,释放持有的对象锁,线程状态有 Running 变为 Waiting,并将当前线程放置到对象的 等待队列;
  2. 调用notify 或者 notifyAll 方法后,等待线程依旧不会从 wait 返回,需要调用 noitfy 的线程释放锁之后,等待线程才有机会从 wait 返回;
  3. notify 方法:将等待队列的一个等待线程从等待队列种移到同步队列中 ,
  4. notifyAll 方法:将等待队列种所有的线程全部移到同步队列,被移动的线程状态由 Waiting 变为 Blocked。

等待队列(等待池) 同步队列(锁池)

同步队列(锁池):假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的同步队列(锁池)中,这些线程状态为Blocked。

等待队列(等待池):假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时 线程A就进入到了该对象的等待队列(等待池)中,此时线程A状态为Waiting。如果另外的一个线程调用了相同对象的notifyAll()方法,那么 处于该对象的等待池中的线程就会全部进入该对象的同步队列(锁池)中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么 仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的同步队列(锁池)。


深入join()

join() : 保证线程的执行顺序

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        MyThread1 t=new MyThread1();
        t.start();
        t.join();
        System.out.println(Thread.currentThread().getName()+"运行");
    	}
    }


class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + "执行!");
        }
    }
}

一句话总结: 让获得这个线程锁(t1)的线程(main) 等待,t1 执行完再唤醒线程(main),这样就保证了执行顺序。

看完下面的分析就可以更好理解上面的总结

JDK中 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;  
        }  
    }  
}  

join的实现就是调用wait()方法。

wait() 方法源码

public final native void wait(long timeout) throws InterruptedException;

该方法为本地方法,调用此方法的当前线程需要释放锁,并等待唤醒。在上述例子中,主线程调用子线程对象的join()方法,因此主线程在此位置需要释放锁,并进行等待。

main线程运行,运行到t.join(等待时间)时,相当于运行 t.wait(),main线程获得线程对象 t 的锁,调用wait(等待时间)方法,main线程又释放掉 线程对象t 的锁,从 running变为 blocked 进入等待队列,等待线程对象t唤醒。

那么join()方法在什么时候唤醒 main线程?

查看Thread类中存在exit()方法,源码如下:

private void exit() {
        if (group != null) {                //线程组在Thread初始化时创建,存有创建的子线程
            group.threadTerminated(this);   //调用threadTerminated()方法
            group = null;
        }
        
        target = null;
        
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

exit()在线程执行完run()方法之后会被调用,此时线程组中存在当前子线程,因此会调用线程组的threadTerminated()方法。 查看ThreadGroup.threadTerminated()方法源码:

void threadTerminated(Thread t) {
        synchronized (this) {
            remove(t);                          //从线程组中删除此线程

            if (nthreads == 0) {                //当线程组中线程数为0时
                notifyAll();                    //唤醒所有待定中的线程
            }
            if (daemon && (nthreads == 0) &&
                (nUnstartedThreads == 0) && (ngroups == 0))
            {
                destroy();
            }
        }
    }

通过此方法,将子线程从线程组中删除,并唤醒其他等待的线程。在上述例子中,此时子线程被销毁,并释放占用的资源,并唤醒等待中的线程。而在join()方法中等待的主线程被唤醒,并获得锁,继续往下执行 t.join() 后面的代码。

参考资料:

https://juejin.cn/post/6844903624842149895#comment

https://juejin.cn/post/6844903558433734669#heading-13

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/271930.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号