多线程——wait和notify

        wait和notify的引入

        试想一个这样的场景,ABC三人去atm取钱,当A进入atm后,就会锁上门,也就是对门加锁,此时BC想要进入atm就要等待A开门,也就是释放锁。但是当A进去后发现,atm里没钱,需要等工作人员往里面补钱,所以他就出来了。但是出来之后,他想要知道有钱了没有,所以又需要再进去。当A出来后,就会和BC一起参与到“门”这个锁的竞争中,但是,在这个竞争中,刚刚释放锁的A是有优势的,也就是大概率A又会重新拿到这个锁。如果一直重复下去,就会导致BC无法正确完成工作,而银行工作人员也无法补充现金,导致这个场景卡死。

        用代码表示大致是这样:

public static void main(String[] args) {
        Thread A = new Thread(() ->{
            while(true){
                synchronized (门){
                    if (有钱) {
                        //取钱
                        break;
                    } else {

                    }
                }
            }
        });
        Thread B = new Thread(() ->{
            while(true){
                synchronized (门){
                    if (有钱) {
                        //取钱
                        break;
                    } else {

                    }
                }
            }
        });
        Thread C = new Thread(() ->{
            while(true){
                synchronized (门){
                    if (有钱) {
                        //取钱
                        break;
                    } else {

                    }
                }
            }
        });
    }

        那如何让A出来后不再进入,而是收到通知后再进入呢?

        这就需要使用到wait和notify了

        wait的使用

        wait的主要作用就是让线程进入等待状态。

        wait是Object类的一个方法,可以选择不传入参数,传入一个参数,传入两个参数

        如果不传入参数,那么线程就会无限等待下去,直至被唤醒然后解除等待状态。

        如果传入参数,那么线程会在时间结束或者被主动唤醒的情况下解除等待状态。   

        使用wait时,wait会进行以下操作:

        使当前执⾏代码的线程进⾏等待.(把线程放到等待队列中)
        释放当前的锁
        满⾜⼀定条件时被唤醒,重新尝试获取这个锁.

        wait的使用一定要配合synchronized来使用,脱离synchronized的wait会直接抛出异常

public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        object.wait();
    }

        wait结束的条件:

        其他线程使用notify方法

        等待超时

        其他线程调⽤该等待线程的interrupted⽅法,导致wait抛出InterruptedException异常

        wait使用案例:

public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t = new Thread(() ->{
            synchronized (lock){
                System.out.println("wait之前");
                try {
                    lock.wait(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait之后");
            }
        });
        t.start();
        Thread.sleep(3000);
        System.out.println("3s过去了");
    }

        

        可以看到,进入wait后五秒触发超时,wait结束,线程解除等待,然后正常执行.

        notify的使用

        norify的作用时唤醒正在wait的线程,有以下几个注意事项:

        ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。

        如果有多个线程等待,则有线程调度器随机挑选出⼀个呈wait状态的线程。(并没有"先来后到")

        在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏完,也就是退出同步代码块之后才会释放对象锁。

        示例,让wait提前唤醒:

public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(() ->{
            synchronized (lock){
                System.out.println("wait之前");
                try {
                    lock.wait(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait之后");
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (lock){
                lock.notify();
            }
        });
        t1.start();
        t2.start();
        Thread.sleep(3000);
        System.out.println("3s过去了");
    }

        

        可以看到,由于notify提前唤醒了wait,所以再输出“wait之前”后输出的时“wait之后”再等待3s才输出“3s过去了”.

        notifyAll

        notifyAll的用法基本一致,唯一的区别是:notifyAll会将所有等待的线程一齐唤醒,让他们重新进入锁的竞争。

        解决刚开始的问题

        有了这两个方法,就可以完美的解决这个问题,只需要在A观察到没有钱后,让他进入wait中,等待工作人员的notiy即可,代码可以这样表示:

public static void main(String[] args) {
        Object men = new Object();
        Thread A = new Thread(() ->{
            while(true){
                synchronized (men){
                    if (有钱) {
                        //取钱
                        break;
                    } else {
                        try {
                            men.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        Thread B = new Thread(() ->{
            while(true){
                synchronized (men){
                    if (有钱) {
                        //取钱
                        break;
                    } else {
                        try {
                            men.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        Thread C = new Thread(() ->{
            while(true){
                synchronized (men){
                    if (有钱) {
                        //取钱
                        break;
                    } else {
                        try {
                            men.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        Thread worker = new Thread(()->{
            synchronized (men){
                //补钱
                men.notifyAll();//通知所有人已经补钱
            }
        });
    }

        在worker唤醒所有人后,ABC会重新来竞争“门”这个锁,三个线程谁会拿到这个锁时随机的。

        wait和sleep的区别

        从等待时间上,wait可以无限等,而sleep必须设有一个时间,超过时间将自动唤醒

        从唤醒上,wait是通过notify来通知唤醒,而sleep只能强制interrupt唤醒

        从使用上,wait需要搭配synchronized使⽤,sleep不需要。wait一般用于不知道要等待多久,超时时间更像是一个保险措施来“兜底”,而sleep是知道要等待多久的

        从从属关系上,wait是object的一个方法,而sleep是Thread的一个方法

          

### Java 多线程中 `wait` `notify` 方法的工作原理 在Java多线程编程环境中,`wait()` `notify()` 是用于协调不同线程之间操作的重要工具。这些方法通常与对象的监视器(即锁)一起工作来控制线程间的交互。 当某个线程调用了对象上的 `wait()` 方法时,它会释放对该对象持有的锁,并进入等待状态直到另一个线程调用同一对象上的 `notify()` 或者 `notifyAll()` 方法将其唤醒[^1]。值得注意的是,在调用这两个方法之前,必须先获取到相应的对象锁;也就是说,它们应该放在由 synchronized 关键字修饰的方法或语句块内执行,否则将会抛出 IllegalMonitorStateException 异常[^2]。 一旦有线程进入了等待队列,那么除非发生特定事件——比如其他线程通过 `notify()/notifyAll()` 显式地通知、超时时间到达或是发生了虚假唤醒现象——否则这个线程将继续保持休眠状态而不参与CPU资源的竞争[^3]。 ### 导致行为类似于单线程的原因解释 尽管存在并发的可能性,但在某些情况下使用不当可能会让程序表现出如同运行于单一进程中的效果: - **串行化访问**:由于每次只有一个线程能够持有给定对象的锁定,因此即使应用程序中有多个活动线程,对于受保护区域内的代码来说也只会有一个线程可以真正执行。这实际上就形成了序列化的处理流程。 - **过度依赖同步机制**:如果开发者频繁地利用 `synchronized` 来包裹大部分逻辑单元,甚至整个函数体都被设为临界区的话,那就会极大地削弱并行度的优势,使得各个任务不得不排队等候前一个完成后再继续下去。 - **不恰当的通知策略**:假设在一个场景里头,生产者负责往缓冲池添加数据项而消费者则从中取出消费掉。此时若仅依靠简单的 `if (condition)` 结构配合 `wait/notify` 实现消息传递,则很容易陷入死循环或者饥饿问题之中,进而影响整体性能表现得像单一线程那样缓慢低效。 ```java public class BoundedBuffer { private final Object[] items; private int putptr, takeptr, count; public BoundedBuffer(int capacity) { ... } public synchronized void put(Object x) throws InterruptedException { while (count == items.length) wait(); // 如果满了就等着 items[putptr++] = x; if (++putptr == items.length) putptr = 0; ++count; notify(); } } ``` 上述例子展示了如何正确运用 `wait()` `notify()` 进行跨线程通讯的同时避免不必要的阻塞所造成的效率损失。然而实际开发过程中还需考虑更多边界情况以确保系统的稳定性响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值