Java多线程系列之wait

前言

我们知道让线程阻塞除了可以调用sleep方法,join方法还有wait方法,前两个是属于Tread的方法,而wait是属于Object的方法,今天就来聊一聊wait的用法。


先看一看wait的三个重载方法

  • public final void wait() throws InterruptedException
  • public final void wait(long timeout) throws InterruptedException
  • public final void wait(long timeout, int nanos) throws InterruptedException

第一个跟第三个方法最终调用的都是第二个方法,第一个方法相当于调用wait(0),0表示永远不超时。一旦调用的对象的wait方法,那么只有调用对象的notify或者notifyAll才能将其唤醒,或者阻塞时间达到了timeout时间也会自动唤醒。

每一个对象都有一个跟它关联的monitor,只有获取到对象的monitor才能调用对象的wait方法和调用对象的notify和notifyAll方法。也就是说wait,notify,notifyAll都必须在对象的synchronized同步方法里面调用。如果wait没有在对象的synchronized同步块里面执行会抛出

java.lang.IllegalMonitorStateException

例如执行以下方法:

private Object lock = new Object();  
public void block(){  
  try {  
     lock.wait();  
  } catch (InterruptedException e) {  
     e.printStackTrace();  
  }  
}

抛出异常

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Object.wait(Object.java:326)
    at com.bzl.dp.eqpt.dev.TestWait.block(TestWait.java:28)
    at com.bzl.dp.eqpt.dev.TestWait.lambda$main$0(TestWait.java:14)
    at java.base/java.lang.Thread.run(Thread.java:835)

使用notify唤醒wait

看下面例子执行结果是什么

public static void main(String[] args) {  
  TestWait tw = new TestWait();  
  Thread t1 = new Thread(() -> tw.block());
  t1.start();  
  //短暂休眠以便让block先执行  
  try {  
        Thread.sleep(10L);  
  } catch (InterruptedException e) {  
        e.printStackTrace();  
  }  
  tw.release();  
}  
  
private Object lock = new Object();  
  
public void block() {  
    synchronized (lock) {  
        try {  
            System.out.println("before wait");  
            lock.wait();  
            System.out.println("after wait");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
  
public void release() {  
    synchronized (lock) {  
        lock.notify();  
        System.out.println("after release");  
    }  
}

运行结果

before wait
after release
after wait

具体流程如下:
首先我们启动了一个新的线程执行了lock的wait方法,然后在主线程去唤醒执行了wait的线程,为了让block方法先执行,这里还短暂的休眠了一会儿

  • 1.当t1执行block方法的synchronized块时,进入lock对象的锁池,由于现在lock的monitor还没有其他线程持有,所有t1抢占了lock的monitor,打印了before wait,当执行到lock.wait()时,释放了lock的monitor并且进入lock对象的等待池,等待被唤醒
  • 2.主线程执行release方法,由于t1已经释放了lock的monitor,所以当release执行到synchronized块的时候,进去到lock的锁池,并且成功的获取到了lock的monitor,调用lock.notify(),这时把t1从等待池里唤醒(如果有多个线程在等待池里,notify只能随机唤醒一个,需要调用notifyAll才能把等待池的所有线程唤醒),并且进入到lock的锁池,由于release方法还没执行完同步块代码所以主线程还持有lock的monitor,所以t1要等lock的monitor的释放,当主线程打印after release退出同步块释放lock的monitor。
  • 3.这时t1才从锁池成功的获取到lock的monitor,最后打印after wait

完整流程图如下:

wait-notify流程

使用interrupt()方法中断wait

除了使用notify或notifyAll,还可以使用interrupt方法来中断wait,当使用interrupt中断wait时,会抛出InterruptedException异常
代码如下:

public static void main(String[] args) {  
    TestInterrupt tw = new TestInterrupt();  
    Thread t1 = new Thread(() -> tw.block());  
    t1.start();  
    new Thread(() -> tw.interrupt(t1)).start();  
}  
  
private Object lock = new Object();  
  
public void block() {  
    synchronized (lock) {  
        try {  
            System.out.println("before wait");  
            lock.wait();  
            System.out.println("after wait");  
        } catch (InterruptedException e) {  
            System.out.println("Thread is interrupted");  
            e.printStackTrace();  
        }  
    }  
}  
  
public void interrupt(Thread t) {  
    try {  
        Thread.sleep(1000L);  
  } catch (InterruptedException e) {  
        e.printStackTrace();  
  }  
    synchronized (lock) {  
        t.interrupt();  
        try {  
            Thread.sleep(1000L);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Interrupt the thread.");  
    }  
}

运行结果:

before wait
Interrupt the thread.
Thread is interrupted
java.lang.InterruptedException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Object.wait(Object.java:326)
    at com.bzl.dp.eqpt.dev.TestInterrupt.block(TestInterrupt.java:24)
    at com.bzl.dp.eqpt.dev.TestInterrupt.lambda$main$0(TestInterrupt.java:13)
    at java.base/java.lang.Thread.run(Thread.java:835)

具体流程如下:

  • 1.创建线程t1,执行block方法,t1进去lock的锁池,并且获取lock的monitor,打印before wait, t1释放lock的monitor,并且进去lock的等待池,等待被唤醒。
  • 2.创建新线程,并且在新线程执行interrupt方法,入参为t1,新线程休眠1秒,新线程进入lock锁池并且获取lock的monitor,执行t1线程的interrupt方法,把t1线程从等待池唤醒,并且进入lock的锁池,等待获取lock的monitor,因为现在新线程还没有执行完同步块,还持有lock的monitor,所以t1线程还在堵塞状态,当新线程休眠完1秒并且打印Interrupt the thread退出同步块,释放monitor。(interrupt方法没有要求一定要在synchronized块里面执行,这里只是演示t1线程被执行interrupt后需要获取monitor才能继续执行,所以这里加了一个synchronized)
  • 3.t1在锁池获取到lock的monitor,因为t1使被调用interrupt唤醒的,所以收到一个InterruptedException异常,并且interrupt状态会被清空,这时异常被捕获,跳过打印after wait,最后执行Thread is interrupted

完成流程图如下:

wait-interrupt流程

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java中的wait和notify是多线程编程中的两个重要方法,用于线程之间的协作和通信。 wait方法可以使当前线程进入等待状态,直到其他线程调用notify或notifyAll方法唤醒它。在调用wait方法时,当前线程会释放它所持有的锁,以便其他线程可以访问共享资源。 notify方法用于唤醒一个处于等待状态的线程,如果有多个线程在等待,则只会唤醒其中一个线程。notifyAll方法则会唤醒所有处于等待状态的线程。 wait和notify方法必须在同步块中使用,即在使用这两个方法的对象上获取锁。否则会抛出IllegalMonitorStateException异常。 使用wait和notify方法可以实现线程之间的协作和通信,例如生产者消费者模型。在生产者消费者模型中,生产者线程生产数据并将其放入共享队列中,消费者线程从队列中取出数据并进行消费。当队列为空时,消费者线程需要等待生产者线程生产数据,此时可以使用wait方法使消费者线程进入等待状态。当生产者线程生产数据并将其放入队列中时,可以使用notify方法唤醒处于等待状态的消费者线程。 ### 回答2: Java线程中的 wait 和 notify 是两个非常重要的方法,它们可以帮助线程之间达成协作,实现复杂的操作。wait 方法用于让当前线程进入等待状态,直到其他线程通过 notify 方法通知它继续执行。notify 方法则用于唤醒一个等待状态的线程,使其继续执行。 wait 方法 wait 方法用于让当前线程进入等待状态,直到其他线程通过 notify 或 notifyAll 方法唤醒它。wait 方法需要在 synchronized 代码块中使用,否则会抛出 IllegalMonitorStateException 异常。在进入等待状态之后,线程将释放锁,并且进入一个等待池中,等待其他线程调用 notify 或 notifyAll 方法唤醒它。 notify 方法 notify 方法用于唤醒一个等待状态的线程,使其继续执行。notify 方法同样需要在 synchronized 代码块中使用,否则同样会抛出 IllegalMonitorStateException 异常。当一个线程调用 notify 方法时,等待池中的线程将会被唤醒,但是它们不能马上继续执行,必须等待当前线程释放锁。如果有多个线程在等待池中,notify 方法只会唤醒其中一个线程,具体唤醒哪个线程是随机的。 notifyAll 方法 notifyAll 方法与 notify 方法类似,但是它会唤醒所有等待池中的线程。notifyAll 方法同样需要在 synchronized 代码块中使用。 使用 wait 和 notify 实现线程协作 wait 和 notify 方法可以用来实现线程之间的协作,例如生产者和消费者问题。假设我们有一个共享的队列,生产者向队列中添加数据,消费者从队列中取出数据。如果队列已经满了,生产者就需要等待消费者取走数据,如果队列是空的,消费者就需要等待生产者加入新数据。 在这个问题中,我们可以使用 wait 和 notify 方法来实现线程之间的协作,代码如下: ``` public class Queue { private final List<Integer> items = new LinkedList<>(); private static final int MAX_SIZE = 10; public synchronized void produce(int item) throws InterruptedException { while (items.size() == MAX_SIZE) { wait(); } items.add(item); notify(); } public synchronized int consume() throws InterruptedException { while (items.isEmpty()) { wait(); } int item = items.remove(0); notify(); return item; } } public class Producer implements Runnable { private final Queue queue; public Producer(Queue queue) { this.queue = queue; } public void run() { for (int i = 0; i < 20; i++) { try { queue.produce(i); System.out.println("Produced: " + i); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public class Consumer implements Runnable { private final Queue queue; public Consumer(Queue queue) { this.queue = queue; } public void run() { for (int i = 0; i < 20; i++) { try { int item = queue.consume(); System.out.println("Consumed: " + item); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public class Main { public static void main(String[] args) throws InterruptedException { Queue queue = new Queue(); Thread producer = new Thread(new Producer(queue)); Thread consumer = new Thread(new Consumer(queue)); producer.start(); consumer.start(); producer.join(); consumer.join(); } } ``` 在这个示例代码中,我们创建了一个 Queue 类,它有两个方法 produce 和 consume 用于生产和消费数据。在 produce 方法中,我们使用 while 循环来等待队列不满,如果队列已经满了,就调用 wait 方法进入等待状态。在 consume 方法中,我们使用 while 循环来等待队列不空,如果队列是空的,就调用 wait 方法进入等待状态。在生产新数据或者消费数据之后,我们都调用 notify 方法来唤醒等待池中的线程。 最后,我们可以使用 Producer 和 Consumer 类来生产和消费数据,它们分别运行在不同的线程中。在运行这个程序时,生产者将不断生产数据,消费者将不断消费数据,一直到数据生产完毕为止。在这个过程中,生产者和消费者之间通过 wait 和 notify 方法实现了线程之间的协作。 ### 回答3: Java是一种支持多线程的编程语言,在多线程编程过程中,一个线程可能需要等待另一个线程的某个条件满足后才能继续执行。Java提供了wait和notify来实现线程之间的协作。 wait:使当前线程进入等待状态,释放对象的锁,直到其他线程调用notify或notifyAll方法唤醒它。wait方法必须在持有对象锁的情况下调用,否则会抛出IllegalMonitorStateException异常。 notify:唤醒一个处于等待状态的线程,如果有多个线程等待,则只会唤醒其中一个,具体唤醒哪个线程无法预测。 notifyAll:唤醒所有处于等待状态的线程。 wait和notify必须在同步代码块中调用,并且针对同一个对象。wait和notify的调用顺序也非常重要,如果先调用了notify而没有等待线程,会导致唤醒失效。 在多线程编程中,wait和notify常常用于生产者和消费者模式中的线程之间的通信,生产者线程在生产完毕后调用notify方法唤醒消费者线程来消费数据,消费者线程在消费完毕后调用wait方法等待下一个生产者线程的唤醒。 wait和notify的使用需要谨慎,如果使用不当,会导致死锁或线程饥饿等问题。同时,在Java SE 5之后,Java提供了更加高级的线程库,如ReentrantLock、Condition等,可以更加方便和安全地实现线程之间的协作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值