关键字wait和notify

本文探讨了线程控制中的核心问题:如何确保线程按照预期顺序执行。介绍了wait和notify方法的应用,以及它们与synchronized关键字的配合使用。通过实例演示了如何利用这些方法实现精确的线程调度。

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

前言:

线程最大的问题就是抢占式执行,随机调度,而我们写的代码,不希望它是随机的,随机就代表着不可控,出现bug的概率会大大提高.

所以,程序员发明了一些办法,来控制程序之间的执行顺序.虽然线程在内核里的调度是随机的,但是可以通过一些api让线程主动阻塞,主动放弃cpu(给别的线程让路).

比如,t1和t2两个线程,希望t1先执行,执行的差不多了,在让t2来干.就可以让t2先wait(阻塞,主动放弃cpu),等t1执行的差不多了,再通过notify来通知t2,把t2唤醒,让t2接着干.

那么上述场景,使用join或者sleep行不行呢?

使用join,则必须让t1彻底执行完,t2才能运行.如果是希望t1先干50%的活,就让t2开始行动,此时join无能为力.

使用sleep,指定一个休眠的时间.但是t1执行完这些代码,到底花了多少时间,不好估计.

使用wait和notify可以更好的解决上述的问题.


wait:

wait进行阻塞,某个线程调用wait方法,就会进入阻塞(无论是通过哪个对象wait的),此时就处在WAITING状态.

wait,notify和notifyAll这几个类都是Object类的方法,所以Java里随便一个对象,都可以有这三种方法.

注意,wait也需要这个异常,这个异常,很多带有阻塞功能的方法都带.

这些方法都是可以被interrupt方法通过这个异常给唤醒的.

wait不加任何参数,就是一个"死等".一直等待,直到有其他线程唤醒它.

我们运行这段代码,会发现日志里报了这样的异常.

 IllegalMonitorStateException,非法的锁状态异常.

为什么会有这个异常??

要理解wait要进行哪些操作:

  • 1.先释放锁
  • 2.进行阻塞等待
  • 3.收到通知之后,重新尝试获取锁,并且在获取锁后,继续往下执行.

那么,上述的锁状态异常就是因为,还没加锁呢,就要释放锁.

因此,wait操作需要搭配synchronized来使用.

 

 搭配synchronized之后,我们可以看到,main线程阻塞等待了.

虽然这里wait是阻塞了,阻塞在synchronized的代码块里,但实际上,这里的阻塞时释放了锁的.

此时其他的线程是可以获取到object这个对象的锁的.此时这里的阻塞,就处在waiting状态.


wait和notify的实现过程

 务必要这四个对象都相同,才能够正确生效.

此处的通知得和wait配对,如果wait使用的对象和notify使用的对象,不同,此时的notify不会有任何的效果.(notify只能唤醒在同一个对象上等待的线程).

如果代码这里写作t1.start 和 t2.start,由于线程调度的随机性,此时不能保证一定是先执行wait,后执行notify,

如果调用notify,此时没有线程wait,此处的wait是无法被唤醒的.(相当于notify空打了一炮).

这种通知就是无效通知,也没有什么副作用.

因此此处的代码,还是要尽量保证先执行wait,后执行notify.才是有意义的.

运行这段代码:

 此处,先执行了wait,很明显wait操作阻塞了,没有看到wait之后的打印;接下来,执行到了t2,t2进行了notify的时候,才会把t1的wait唤醒,t1才能继续执行.

只要t2不执行notify,此时的t1就会始终wait下去.(死等不是一个很好的选择)


wait无参数版本,就是死等的.

wait带参数的版本,指定了等待的最大时间.

wait带参数的版本和sleep的区别:

虽然都是指定等待的时间,虽然也都能被提前唤醒(wait使用的是notify唤醒,sleep使用的是interrupt唤醒),但是这里表示的含义截然不同.

notify唤醒wait,这里不会有任何的异常(正常的业务逻辑).

interrupt唤醒sleep,则是出异常(表示一个出问题的逻辑).


如果当前有多个线程在等待object对象,此时有一个线程object.notify(),此时是随机唤醒一个等待的线程(不知道具体是哪一个).

但是,这种场景下,我们可以用多组不同的对象,来指定唤醒的顺序.

public class ThreadDemo18 {
    public static void main(String[] args) {
        //有三个线程,分别只能打印A,B,C,控制三个线程固定按照ABC的顺序打印
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread t1 = new Thread(()->{
            System.out.println("A");
            synchronized (locker1) {
                locker1.notify();
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker1) {
                try {
                    locker1.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("B");
            synchronized (locker2) {
                locker2.notify();
            }
        });
        Thread t3 = new Thread(()->{
            synchronized (locker2) {
                try {
                    locker2.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("C");
        });
        t2.start();
        t3.start();
        //此处加sleep,是为了保证t2,t3线程先执行,先让这两个线程获取到锁,防止t1产生无效通知.
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.start();
    }
}

notifyAll和notify很相似,多个线程wait的时候,notify随机唤醒一个,notify则是所有的线程都唤醒,这些线程再一起竞争锁. 

<think>好的,我现在需要回答用户关于Java中synchronized、waitnotify的用法及关系的问题。首先,我需要回忆这些关键字的基本概念它们在多线程编程中的作用。 synchronized是Java中用于实现同步的关键字,可以修饰方法或代码块,确保同一时间只有一个线程能执行该段代码。这解决了线程安全问题,防止多个线程同时访问共享资源导致的数据不一致。例如,当多个线程修改同一个变量时,使用synchronized可以确保操作的原子性。 接下来是waitnotify方法。这两个方法属于Object类,必须在synchronized块或方法中使用,否则会抛出IllegalMonitorStateException。wait()的作用是让当前线程释放锁并进入等待状态,直到其他线程调用notify()或notifyAll()来唤醒它。而notify()用于唤醒在此对象监视器上等待的单个线程,如果有多个线程在等待,则选择其中一个唤醒。notifyAll()则会唤醒所有等待的线程,让它们竞争锁。 用户可能想知道这些机制的使用场景。例如,生产者-消费者模型是一个经典案例。生产者生产数据时,如果缓冲区已满,就需要等待,直到消费者消费了数据后通知生产者继续生产。同样,消费者在缓冲区为空时等待,直到生产者放入数据后通知消费者。这里,synchronized保护共享缓冲区,waitnotify用于线程间的协调。 需要注意的是,wait()通常放在循环中检查条件,防止虚假唤醒。即使线程被唤醒,也可能条件仍未满足,所以需要再次检查。例如: synchronized (lock) { while (conditionNotMet) { lock.wait(); } // 执行操作 } 此外,wait()可以指定超时时间,避免无限等待。而notify()notifyAll()的选择取决于是否需要唤醒所有等待线程,比如在资源可用时,使用notifyAll()让所有等待线程竞争,可能更公平,但性能开销更大。 用户可能还会疑惑这些方法与LockCondition的区别。Java并发包中的Lock提供了更灵活的锁机制,Condition则允许更细粒度的等待/通知控制,但synchronizedwait/notify是更基础的机制,适用于简单场景。 在引用中提到的外卖小哥例子,说明了在没有协调机制的情况下,线程可能需要不断轮询,效率低下。使用wait/notify可以让线程在条件不满足时等待,而不是忙等,从而节省资源。 总结来说,synchronized用于互斥,确保线程安全;waitnotify用于线程间协作,避免资源浪费。正确使用它们可以编写高效的多线程程序。</think>### Java中synchronized、waitnotify的用法及关系 #### 1. **synchronized的作用与使用场景** - **功能**:`synchronized` 是Java中实现线程同步的关键字,用于修饰代码块或方法,确保同一时刻只有一个线程能执行被保护的代码,从而解决共享资源的线程安全问题[^3]。 - **使用场景**: - 多线程访问共享变量时(如计数器、缓冲区)。 - 需要保证操作的原子性(如银行转账)。 - **示例**: ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } } ``` #### 2. **waitnotify的作用与协作** - **功能**: - `wait()`:使当前线程释放锁并进入等待状态,直到其他线程调用`notify()`或`notifyAll()`唤醒它。**必须**在`synchronized`块中使用[^2]。 - `notify()`:唤醒一个等待该对象锁的线程;`notifyAll()`:唤醒所有等待的线程。 - **协作机制**: - 典型场景为**生产者-消费者模型**: - 生产者生产数据时,若缓冲区满,则调用`wait()`等待消费者消费。 - 消费者消费数据后,调用`notify()`唤醒生产者继续生产。 - **示例**: ```java public class Buffer { private Queue<Integer> queue = new LinkedList<>(); private int capacity = 5; public synchronized void produce(int value) throws InterruptedException { while (queue.size() == capacity) { wait(); // 缓冲区满,等待 } queue.add(value); notifyAll(); // 唤醒所有等待的消费者 } public synchronized int consume() throws InterruptedException { while (queue.isEmpty()) { wait(); // 缓冲区空,等待 } int value = queue.poll(); notifyAll(); // 唤醒所有等待的生产者 return value; } } ``` #### 3. **关键区别与注意事项** - **锁的释放**: - `synchronized`执行完毕后自动释放锁。 - `wait()`会**主动释放锁**,而`notify()`仅唤醒线程,**不会释放锁**,需等待同步块结束。 - **条件检查**: - `wait()`应始终在循环中调用,避免虚假唤醒(即使未被通知,线程也可能被唤醒): ```java synchronized (lock) { while (conditionNotMet) { lock.wait(); } // 执行操作 } ``` - **异常处理**: - 若在非同步代码中调用`wait()`或`notify()`,会抛出`IllegalMonitorStateException`[^2]。 #### 4. **典型问题与优化** - **死锁风险**: - 若多个线程互相等待且无唤醒机制,可能导致死锁。需确保逻辑上每个等待最终都能被唤醒。 - **性能优化**: - 使用`notifyAll()`可能更安全,但会引入竞争;若明确只有单个线程需唤醒,则用`notify()`。 - 可结合`java.util.concurrent`包中的高级工具(如`Lock``Condition`)实现更精细的控制。 #### 5. **应用场景** - **生产者-消费者模型**:如任务队列、消息队列。 - **线程池任务调度**:工作线程等待任务时调用`wait()`,提交任务后调用`notify()`。 - **资源池管理**:数据库连接池的分配与回收。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值