Java多线程wait()和notify()系列方法使用教程(内涵故事)

简介

本文讲解Java中wait()notify(),通过一个标准的使用实例,来讨论下这两个方法的作用和使用时注意点,这两个方法被提取到顶级父类Object对象中,地位等同于toString()方法,所以本文带你从零开始搞懂它们的用法,在文章最后,准备了一个《捡肥皂》的故事,就算你没写过代码,读了此故事也能明白wait()notify()系列方法在程序中的作用了,也算是在1024程序员节送给大家的彩蛋了。

一.wait()和notify()系列方法含义

wait()方法是让当前线程等待的,即让线程释放了对共享对象的锁。

wait(long timeout)方法可以指定一个超时时间,过了这个时间如果没有被notify()唤醒,则函数还是会返回。如果传递一个负数timeout会抛出IllegalArgumentException异常。

notify()方法会让调用了wait()系列方法的一个线程释放锁,并通知其它正在等待(调用了wait()方法)的线程得到锁。

notifyAll()方法会唤醒所有在共享变量上由于调用wait系列方法而被挂起的线程。

注意:

  1. 调用wait()notify()方法时,当前线程必须要成功获得锁(必须写在同步代码块锁中),否则将抛出异常。
  2. 只对当前单个共享变量生效,多个共享变量需要多次调用wait()方法。
  3. 如果线程A调用wait()方法后处于堵塞状态时,其他线程中断(在其他线程调用A.interrupt()方法)A线程,则会抛出InterruptExcption异常而返回并终止。

理论内容就这些,下面将上述内容用实例展示给大家,并一步一步带着大家分析和实现这两个方法,多线程中这两个方法会让程序跳跃执行,所以一定要搞清楚代码的执行流程。

二.标准代码示例

1.代码实现内容流程描述:

  1. 创建两个线程Thread0Thread1
  2. Thread0执行wait()方法。
  3. 此时Thread1得到锁,再让Thread1执行notify()方法释放锁。
  4. 此时Thread0得到锁,Thread0会自动从wait()方法之后的代码,继续执行。

通过上述流程,我们就可以清楚的看到,wait()notify()各自是怎么工作的了,也可以知道两者是怎么配合的了。

2.代码实现:

public class ThreadWaitAndNotify {

	// 创建一个将被两个线程同时访问的共享对象
	public static Object object = new Object();

    // Thread0线程,执行wait()方法
	static class Thread0 extends Thread {

		@Override
		public void run() {
			synchronized (object) {
				System.out.println(Thread.currentThread().getName() + "初次获得对象锁,执行中,调用共享对象的wait()方法...");
				try {
					// 共享对象wait方法,会让线程释放锁。
					object.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "再次获得对象锁,执行结束");
			}
		}

	}

    // Thread1线程,执行notify()方法
	static class Thread1 extends Thread {

		@Override
		public void run() {
			synchronized (object) {
				// 线程共享对象,通过notify()方法,释放锁并通知其他线程可以得到锁
				object.notify();
				System.out.println(Thread.currentThread().getName() + "获得对象锁,执行中,调用了共享对象的notify()方法");
			}
		}
	}

    // 主线程
	public static void main(String[] args) {
		Thread0 thread0 = new Thread0();
		Thread1 thread1 = new Thread1();
		thread0.start();
		try {
			// 保证线程Thread0中的wait()方法优先执行,再执线程Thread1的notify()方法
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread1.start();
	}

3.运行结果


Thread-0初次获得对象锁,执行中,调用共享对象的wait()方法...
Thread-1获得对象锁,执行中,调用了共享对象的notify()方法
Thread-0再次获得对象锁,执行结束

4.运行流程详解:

从执行的结果中,要明白线程的执行顺序:

  1. Thread0调用了wait()方法后,会释放掉对象锁并暂停执行后续代码,即从wait()方法之后到run()方法结束的代码,都将立即暂停执行,这就是wait()方法在线程中的作用。
  2. CPU会将对象锁分配给一直等候的Thread1线程,Thread1执行了notify()方法后,会通知其他正在等待线程(Thread0)得到锁,但会继续执行完自己锁内的代码之后,才会交出锁的控制权。
  3. 因为本例只有两个线程,所以系统会在Thread1交出对象锁控制权后(Synchronized代码块中代码全部执行完后),把锁的控制权给Thread0(若还有其他线程,谁得到锁是随机的,完全看CPU心情),Thread0会接着wait()之后的代码,继续执行到Synchronized代码块结束,将对象锁的控制权交还给CPU。

三.用生活故事讲懂线程的等待唤醒

Java线程的等待唤醒机制,是通过wait()方法和notify()方法实现的,为了更好的理解,我再来举一个通俗易懂接地气的例子吧,帮不懂代码的人也能明白这两个方法的作用。

例:捡肥皂的故事

假设有两个程序员去洗澡,只带了一块肥皂,两个人怎么使用一块肥皂洗澡呢?会发生3个场景:

1.老王和老李(专家程序员):

老王和老李随机一人拿到肥皂,比如老王先拿到肥皂,然后使用肥皂,然后把肥皂让出去,自己等会再用。老李拿到了肥皂,然后使用了一会,再通知老王说:“自己不用了”,老王听到话以后,捡起肥皂从上次用的地方接着用。二者洗澡,你来我往共享一块肥皂,非常和谐。

程序语言描述:
老王随机先得到锁,然后用了一会后,调用了wait()方法,把锁交了出去,自己等待。老李拿到锁,使用后,再通过notify()通知老王,然后等老李用完以后,老王再次拿到锁,继续执行…这种方式是线程安全的,而且还能合理的分配资源的使用,这就是等待唤醒的好处。

2.王哥和李哥(普通程序员):

王哥和李哥随机一人拿到肥皂,比如王哥先拿到,然后王哥就一直霸占着,直到自己洗完了,才把肥皂给李哥。期间李哥洗澡只能干搓,根本没机会接触肥皂。我想李哥肯定觉得王哥很自私,不懂得礼让,李哥的体验不是很好。

程序语言描述:王哥和李哥就是两个线程,王哥在拿到锁以后,就一直使用,直到同步代码块中的内容完全执行完成。再把锁交给李哥使用。这种方式每次都是一个线程执行完,另一个才会执行,是线程安全的。

3.小王和小李(新手程序员):

小王和小李一开始洗澡就争抢肥皂,当肥皂在小王手上时,小王还在使用中,小李就扑上来了,于是出现了两人一起摩擦一块肥皂的场景!这种画面既不优雅,也不安全。

程序语言描述:
如果两个线程,访问同一个资源的时候,不对其进行加锁控制,就会出现混乱的场景,这就是线程不安全。两个线程可能会同时操作同一共享变量,从而使这个共享变量失控,最终结果紊乱。

我们可以看出老王和老李的配合是最默契的,对肥皂这个资源的利用率是最高的。张哥和李哥虽然也能分配资源,但对资源的利用效率不高,始终有一个人,要等待另一个人完全不用了,才能获得。小张和小李是完全不懂配合的,他们都想争抢肥皂,最终是谁都没有用好,场面陷入了混乱,画面不忍直视。。。

本文发表于2020年1024程序员节,以此故事,向各位程序员致敬。1024+996=2020

总结

通过上面故事中的三个例子,我想你应该明白了线程的等待唤醒的使用场景以及好处了吧,你理解了这些通俗的例子,才能在程序中将其思想发挥出来,从而使用wait()notify()方法,构建出一个合理的程序流程,优雅的实现线程之间变量的共享使用。喜欢本文请收藏点赞,祝大家1024节日快乐。

  • 43
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
### 回答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等,可以更加方便和安全地实现线程之间的协作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值