Java多线程:wait()和notify()/notifyAll()

轮询

线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作。

想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件。

这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费在了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。如果可以把这些轮询的时间释放出来,给别的线程用,就好了。

wait/notify

在Object对象中有三个方法wait()、notify()、notifyAll(),既然是Object中的方法,那每个对象自然都是有的。如果不接触多线程的话,这两个方法是不太常见的。下面看一下前两个方法:

1、wait()

wait()的作用是使当前执行代码的线程进行等待,将当前线程置入"预执行队列"中,并且wait()所在的代码处停止执行,直到接到通知或被中断。在调用wait()之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用wait()方法。

2、notify()

notify()的作用是,如果有多个线程等待,那么线程规划器随机挑选出一个wait的线程,对其发出通知notify(),并使它等待获取该对象的对象锁。注意"等待获取该对象的对象锁",这意味着,即使收到了通知,wait的线程也不会马上获取对象锁,必须等待notify()方法的线程释放锁才可以。和wait()一样,notify()也要在同步方法/同步代码块中调用。

总结起来就是,wait()使线程停止运行,notify()使停止运行的线程继续运行。

wait()/notify()使用示例

看一段代码:

 public class MyThread30_0 extends Thread {

        private Object lock;

        public MyThread30_0(Object lock) {

            this.lock = lock;

        }

        public void run() {

            try {

                synchronized (lock) {

                    System.out.println("开始------wait time = " + System.currentTimeMillis());

                    lock.wait();

                    System.out.println("结束------wait time = " + System.currentTimeMillis());

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

    public class MyThread30_1 extends Thread {

        private Object lock;

        public MyThread30_1(Object lock) {

            this.lock = lock;

        }

        public void run() {

            synchronized (lock) {

                System.out.println("开始------notify time = " + System.currentTimeMillis());

                lock.notify();

                System.out.println("结束------notify time = " + System.currentTimeMillis());

            }

        }

    }

写个main函数,同样的Thread.sleep(3000)也是为了保证mt0先运行,这样才能看到wait()和notify()的效果:

  public static void main(String[] args) throws Exception {

        Object lock = new Object();

        MyThread30_0 mt0 = new MyThread30_0(lock);

        mt0.start();

        Thread.sleep(3000);

        MyThread30_1 mt1 = new MyThread30_1(lock);

        mt1.start();

    }

看一下运行结果:

开始------wait time = 1443931599021

开始------notify time = 1443931602024

结束------notify time = 1443931602024

结束------wait time = 1443931602024

第一行和第二行之间的time减一下很明显就是3s,说明wait()之后代码一直暂停,notify()之后代码才开始运行。

wait()方法可以使调用该线程的方法释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。

notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态

notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态 

最后,如果wait()方法和notify()/notifyAll()方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出java.lang.IllegalMonitorStateException,注意一下。

wait()释放锁以及notify()不释放锁

多线程的学习中,任何地方都要关注"锁",wait()和notify()也是这样。wait()方法是释放锁的,写一个例子来证明一下:

public class ThreadDomain31 {

        public void testMethod(Object lock) {

            try {

                synchronized (lock) {

                    System.out.println(Thread.currentThread().getName() + " Begin wait()");

                    lock.wait();

                    System.out.println(Thread.currentThread().getName() + " End wait");

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

    public class MyThread31 extends Thread {

        private Object lock;

        public MyThread31(Object lock) {

            this.lock = lock;

        }

        public void run() {

            ThreadDomain31 td = new ThreadDomain31();

            td.testMethod(lock);

        }

    }

main函数调用一下:

 public static void main(String[] args) {

        Object lock = new Object();

        MyThread31 mt0 = new MyThread31(lock);

        MyThread31 mt1 = new MyThread31(lock);

        mt0.start();

        mt1.start();

    }

看一下运行结果:

Thread-0 Begin wait()

Thread-1 Begin wait()

如果wait()方法不释放锁,那么Thread-1根本不会进入同步代码块打印的,所以,证明完毕。

接下来证明一下notify()方法不释放锁的结论:

public class ThreadDomain32 {

        public void testMethod(Object lock) {

            try {

                synchronized (lock) {

                    System.out.println("Begin wait(), ThreadName = " + Thread.currentThread().getName());

                    lock.wait();

                    System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        public void synNotifyMethod(Object lock) {

            try {

                synchronized (lock) {

                    System.out.println("Begin notify(), ThreadName = " + Thread.currentThread().getName());

                    lock.notify();

                    Thread.sleep(5000);

                    System.out.println("End notify(), ThreadName = " + Thread.currentThread().getName());

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

写两个线程分别调用2个方法:

public class MyThread32_0 extends Thread {

        private Object lock;

        public MyThread32_0(Object lock) {

            this.lock = lock;

        }

        public void run() {

            ThreadDomain32 td = new ThreadDomain32();

            td.testMethod(lock);

        }

    }

    public class MyThread32_1 extends Thread {

        private Object lock;

        public MyThread32_1(Object lock) {

            this.lock = lock;

        }

        public void run() {

            ThreadDomain32 td = new ThreadDomain32();

            td.synNotifyMethod(lock);

        }

    }

写个main函数调用一下:

public static void main(String[] args) throws Exception {

        Object lock = new Object();

        MyThread32_0 mt0 = new MyThread32_0(lock);

        mt0.start();

        MyThread32_1 mt1 = new MyThread32_1(lock);

        mt1.start();

        MyThread32_1 mt2 = new MyThread32_1(lock);

        mt2.start();

    }

看一下运行结果:

Begin wait(), ThreadName = Thread-0

Begin notify(), ThreadName = Thread-1

End notify(), ThreadName = Thread-1

Begin notify(), ThreadName = Thread-2

End notify(), ThreadName = Thread-2

End wait(), ThreadName = Thread-0

如果notify()方法释放锁,那么在Thread-1调用notify()方法后Thread.sleep(5000)必定应该有其他线程可以进入同步代码块了,但是实际上没有,必须等到Thread-1把代码执行完。所以,证明完毕。

interrupt()打断wait()

之前有说过,interrupt()方法的作用不是中断线程,而是在线程阻塞的时候给线程一个中断标识,表示该线程中断。wait()就是"阻塞的一种场景",看一下用interrupt()打断wait()的例子:

public class ThreadDomain33 {

        public void testMethod(Object lock) {

            try {

                synchronized (lock) {

                    System.out.println("Begin wait()");

                    lock.wait();

                    System.out.println("End wait()");

                }

            } catch (InterruptedException e) {

                System.out.println("wait()被interrupt()打断了!");

                e.printStackTrace();

            }

        }

    }

    public class MyThread33 extends Thread {

        private Object lock;

        public MyThread33(Object lock) {

            this.lock = lock;

        }

        public void run() {

            ThreadDomain33 td = new ThreadDomain33();

            td.testMethod(lock);

        }

    }

    public static void main(String[] args) throws Exception {

        Object lock = new Object();

        MyThread33 mt = new MyThread33(lock);

        mt.start();

        Thread.sleep(5000);

        mt.interrupt();

    }

看一下运行结果:

Begin wait()

wait()被interrupt()打断了!

java.lang.InterruptedException

at java.lang.Object.wait(Native Method)

at java.lang.Object.wait(Object.java:485)

at com.xrq.example.e33.ThreadDomain33.testMethod(ThreadDomain33.java:12)

at com.xrq.example.e33.MyThread33.run(MyThread33.java:15)

notifyAll()唤醒所有线程

利用Object对象的notifyAll()方法可以唤醒处于同一监视器下的所有处于wait的线程,举个例子证明一下:

public class ThreadDomain34 {

        public void testMethod(Object lock) {

            try {

                synchronized (lock) {

                    System.out.println("Begin wait(), ThreadName = " + Thread.currentThread().getName());

                    lock.wait();

                    System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

写两个线程,一个调用testMethod(Object lock)的线程,一个notifyAll()线程:

public class MyThread34_0 extends Thread {

        private Object lock;

        public MyThread34_0(Object lock) {

            this.lock = lock;

        }

        public void run() {

            ThreadDomain34 td = new ThreadDomain34();

            td.testMethod(lock);

        }

    }

    public class MyThread34_1 extends Thread {

        private Object lock;

        public MyThread34_1(Object lock) {

            this.lock = lock;

        }

        public void run() {

            synchronized (lock) {

                lock.notifyAll();

            }

        }

    }

main函数开三个wait线程,用一个notifyAll的线程去唤醒:

public static void main(String[] args) throws Exception {

        Object lock = new Object();

        MyThread34_0 mt0 = new MyThread34_0(lock);

        MyThread34_0 mt1 = new MyThread34_0(lock);

        MyThread34_0 mt2 = new MyThread34_0(lock);

        mt0.start();

        mt1.start();

        mt2.start();

        Thread.sleep(1000);

        MyThread34_1 mt3 = new MyThread34_1(lock);

        mt3.start();

    }

看一下运行结果:

Begin wait(), ThreadName = Thread-0

Begin wait(), ThreadName = Thread-2

Begin wait(), ThreadName = Thread-1

End wait(), ThreadName = Thread-1

End wait(), ThreadName = Thread-2

End wait(), ThreadName = Thread-0

当然,唤醒的顺序不重要,因为notifyAll()把处于同一资源下wait的线程全部唤醒,至于唤醒的顺序,就和线程启动的顺序一样,是虚拟机随机的。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Java中的waitnotify多线程编程中的两个重要方法,用于线程之间的协作和通信。 wait方法可以使当前线程进入等待状态,直到其他线程调用notifynotifyAll方法唤醒它。在调用wait方法时,当前线程会释放它所持有的锁,以便其他线程可以访问共享资源。 notify方法用于唤醒一个处于等待状态的线程,如果有多个线程在等待,则只会唤醒其中一个线程。notifyAll方法则会唤醒所有处于等待状态的线程。 waitnotify方法必须在同步块中使用,即在使用这两个方法的对象上获取锁。否则会抛出IllegalMonitorStateException异常。 使用waitnotify方法可以实现线程之间的协作和通信,例如生产者消费者模型。在生产者消费者模型中,生产者线程生产数据并将其放入共享队列中,消费者线程从队列中取出数据并进行消费。当队列为空时,消费者线程需要等待生产者线程生产数据,此时可以使用wait方法使消费者线程进入等待状态。当生产者线程生产数据并将其放入队列中时,可以使用notify方法唤醒处于等待状态的消费者线程。 ### 回答2: Java 多线程中的 waitnotify 是两个非常重要的方法,它们可以帮助线程之间达成协作,实现复杂的操作。wait 方法用于让当前线程进入等待状态,直到其他线程通过 notify 方法通知它继续执行。notify 方法则用于唤醒一个等待状态的线程,使其继续执行。 wait 方法 wait 方法用于让当前线程进入等待状态,直到其他线程通过 notifynotifyAll 方法唤醒它。wait 方法需要在 synchronized 代码块中使用,否则会抛出 IllegalMonitorStateException 异常。在进入等待状态之后,线程将释放锁,并且进入一个等待池中,等待其他线程调用 notifynotifyAll 方法唤醒它。 notify 方法 notify 方法用于唤醒一个等待状态的线程,使其继续执行。notify 方法同样需要在 synchronized 代码块中使用,否则同样会抛出 IllegalMonitorStateException 异常。当一个线程调用 notify 方法时,等待池中的线程将会被唤醒,但是它们不能马上继续执行,必须等待当前线程释放锁。如果有多个线程在等待池中,notify 方法只会唤醒其中一个线程,具体唤醒哪个线程是随机的。 notifyAll 方法 notifyAll 方法与 notify 方法类似,但是它会唤醒所有等待池中的线程。notifyAll 方法同样需要在 synchronized 代码块中使用。 使用 waitnotify 实现线程协作 waitnotify 方法可以用来实现线程之间的协作,例如生产者和消费者问题。假设我们有一个共享的队列,生产者向队列中添加数据,消费者从队列中取出数据。如果队列已经满了,生产者就需要等待消费者取走数据,如果队列是空的,消费者就需要等待生产者加入新数据。 在这个问题中,我们可以使用 waitnotify 方法来实现线程之间的协作,代码如下: ``` 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 类来生产和消费数据,它们分别运行在不同的线程中。在运行这个程序时,生产者将不断生产数据,消费者将不断消费数据,一直到数据生产完毕为止。在这个过程中,生产者和消费者之间通过 waitnotify 方法实现了线程之间的协作。 ### 回答3: Java是一种支持多线程的编程语言,在多线程编程过程中,一个线程可能需要等待另一个线程的某个条件满足后才能继续执行。Java提供了waitnotify来实现线程之间的协作。 wait:使当前线程进入等待状态,释放对象的锁,直到其他线程调用notifynotifyAll方法唤醒它。wait方法必须在持有对象锁的情况下调用,否则会抛出IllegalMonitorStateException异常。 notify:唤醒一个处于等待状态的线程,如果有多个线程等待,则只会唤醒其中一个,具体唤醒哪个线程无法预测。 notifyAll:唤醒所有处于等待状态的线程。 waitnotify必须在同步代码块中调用,并且针对同一个对象。waitnotify的调用顺序也非常重要,如果先调用了notify而没有等待线程,会导致唤醒失效。 在多线程编程中,waitnotify常常用于生产者和消费者模式中的线程之间的通信,生产者线程在生产完毕后调用notify方法唤醒消费者线程来消费数据,消费者线程在消费完毕后调用wait方法等待下一个生产者线程的唤醒。 waitnotify的使用需要谨慎,如果使用不当,会导致死锁或线程饥饿等问题。同时,在Java SE 5之后,Java提供了更加高级的线程库,如ReentrantLock、Condition等,可以更加方便和安全地实现线程之间的协作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java码库

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值