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节日快乐。

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值