Wait/Notify线程通信方式

本文详细探讨了Java线程的状态转换,包括WAITING、TIMED_WAITING和BLOCKED状态之间的关系。讲解了线程如何在锁池和等待队列间切换,以及wait、notify和notifyAll方法的使用。强调了在使用这些方法时需要持有锁以避免线程饿死的问题,并对比了LockSupport.park()与wait()的区别。此外,还介绍了线程的join()方法实现原理,以及其与等待/通知模式的联系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程状态机

在这里插入图片描述
WATING和TIMED_WAITING被唤醒后,是进入BLOCKED阻塞状态,进入阻塞同步队列

线程状态与锁状态关系

在这里插入图片描述
● 锁池状态也就是同步队列,当调用notify和notifyall时,线程只是先进入了BLOCKED状态,只有拿到对象o的锁,才能唤醒线程;当调用wait时,会释放锁,并进入等待队列,同时线程状态变为WAITING。
● wait状态唤醒有三种方式
○ being notified, 即调用notify()或notifyAll()
○ interrupted, 这种方式会抛出InterruptedException异常
○ timing out, 超时

线程通信方式-等待/通知模式

● 调用wait()方法,会释放锁,线程状态由RUNNING->WAITNG,当前线程进入对象等待队列中;
● 调用notify()/notifyAll()方法不会立马释放锁,notify()方法是将等待队列中的线程移到同步队列中,而notifyAll()则是全部移到同步队列中,被移出的线程状态WAITING–>BLOCKED;
● 当前调用notify()/notifyAll()的线程释放锁了才算释放锁,才有机会唤醒wait线程返回。
● 从wait()返回的前提是必须获得调用对象锁,也就是说notify()与notifyAll()释放锁之后,wait()进入BLOCKED状态,如果其他线程有竞争当前锁的话,wait线程继续争取锁资格。
● 使用wait()、notify()、notifyAll()方法时需要先调对象加锁(防止线程饿死)。

模板代码

static Object lock = new Object();
static boolean flag = false;

// 等待线程
synchronized(lock) {
    // 1. 获取lock锁,状态为RUNNING
    while(!flag) {
     	// 2. 进入等待队列,状态为WAITING,释放锁
        // 建议多采用超时等待
        lock.wait();
        
        // 3. 获取锁,状态为RUNNING
    }
    
    // 干活。。。
}


// 通知线程
synchronized(lock) {
    // 1. 获取锁,状态为RUNNING
    flag = true;
    // 2. 通知等待线程,等待线程进入同步队列,等待线程状态为BLOCKED
    lock.notify();
}

// 3. 释放锁

简易版顺序图

在这里插入图片描述

  • wait的执行并不一定要获取lock对象锁,那为何要用synchronized呢?因为如果wait和notify不加锁就执行,那么会存在notify先于wait执行(如下代码),从而造成wait永远没有机会唤醒,这也是为什么建议采用超时等待。
while(!flag) {
    // 不允许在这里提前调用notify
    lock.wait();
}

● 在有加锁的情况下,如果先执行了notify,也没事情,因为有flag来存储是否已经通知过。
● 可参考thread.join的实现

thread.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");
        }
        // 这里的while(){wait(millis)} 就是利用等待/通知中的等待模式,只不过加上了超时设置
        if (millis == 0) {
            // while循环,当线程还活着的时候就一直循环等待,直到线程终止
            while (isAlive()) {
                // 无限期wait等待,释放锁。
                wait(0);
            }
            // 条件满足时返回
        } else {
            while (isAlive()) {
                // 获取下一轮剩余等待时长
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
  • join本质上使用了WAIT/NOTIFY通信模式,线程在死亡时,会调用“this.notifyAll”,从而唤醒join中的线程。

与LockSupport区别

  • LockSupport不需存在死锁问题,如果在调用park前,已经调用了unpark,则调用park会立马返回,因为该线程已经持有许可permit,因此使用LockSupport,不需要synchronize。
  • LockSupport在线程中断时,不会抛出异常,而wait会抛出InterruptedException,因此使用LockSupport时,需要在解除阻塞后,自行判断是否被中断
LockSupport.park(this);
if (Thread.interrupted()) {
    // ....
    throw new InterruptedException();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值