关于wait,notify,notifyall,sleep方法的思考

Posted 写Bug的渣渣高

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于wait,notify,notifyall,sleep方法的思考相关的知识,希望对你有一定的参考价值。

本文概述:
你可能对这几个方法, 已经特别熟悉了. 但是你知道吗

  • 为什么 wait 方法和 notify, notifyAll 定义在 Object 中,sleep 定义在 Thread 中呢
  • 为什么 wait 方法和 notify 方法需要在同步代码块中使用 (如果不在同步代码块使用会怎么样)
  • wait, notify 和 sleep 方法的异同
  • 为什么 wait/notify/notifyAll 被定义在 Object 类中, 而 Sleep 定义在 Thread 中呢?
  • ==如果 wait 方法和 notify 方法不是必须在同步代码块中呢=

wait/notify 和 sleep 方法的异同?

相同点:

  1. 它们都可以让线程阻塞。
  2. 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出InterruptedException 异常。
    不同点:
  3. wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
  4. 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放
    monitor 锁。
  5. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
  6. wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

为什么 wait/notify/notifyAll 被定义在 Object 类中, 而 Sleep 定义在 Thread 中呢?

这里有些前缀知识, 我会简要说明.
如果你了解过 syncrhonized 关键字, 那么就了解, 他可以修饰普通方法, 静态方法, 代码块.
synchronized 底层实现是由 Monitor 来实现的, 如果你不懂, 也只需要看我这里说的即可.
![[Pasted image 20230221094004.png]]
当一个线程进入 synchronized 之后, 底层的 Mointor 对象的 _owner 会记录拥有锁的线程, 其他线程查看的时候都不会进入同步代码块, 这就是锁的原来. 而其可重入性, 也是基于这个原因

持有锁的线程结束之后, 才会释放锁, 其他线程去抢锁.

synchronized 的锁有三种:

  • synchronized 修饰普通方法, 锁为当前的实例对象本身
  • synchronized: 修饰静态方法, 锁位当前类的 Class 对象
  • synchronized 修饰代码块, 可以为自定义的 Object 或者是 this 关键字.

Ok, 前缀知识介绍完毕, 如果你希望了解关于这部分的 synchronized 的轻量锁, 偏向锁, 重量锁, 自适应自旋等, 可以去搜索相关了解, 博主后续也会写该部分的博客.

然后了解一下这些方法吧

  • wait: 释放锁, 当前线程进入等待状态
  • notify: 唤醒一个等待线程
  • notifyAll: 唤醒所有等待的线程
  • sleep: 当前线程进入等待

为什么 wait/notify/notifyAll 要设计在 Object 中呢?

  • 为什么不在 Thread 类中呢? 因为一个线程可能持有多个锁, 比如说一个线程连续执行了两个加了锁的方法, 此时他都获取到了不同的锁对吧, 如果此时调用 wait 或者是调用 notify/notifyAll 方法, 如何确定是对哪个锁进行操作. 并且 wait 之后, 是不是要进入等待状态啊, 进入等待状态, 你得释放全部锁, 不然就死锁了. 如果每次都要释放全部锁, 那么多线程的设计会变得特别粗糙, 比如说一个线程获取了三个锁, 调用 wait 之后, 再次唤醒, 却需要重新和所有的线程竞争锁, 执行时间会变得很慢

wait 和 notify 必须写在同步代码块中

这部分来想象一下, 如果 wait 以及 notify 方法不写在同步代码块中, 会发生什么现象

如果,wait 和 notify 不是在同步代码块中, 就会出现很严重的现象, 在多线程环境中, 我们无法确定 wait 和 notify 哪个先执行, 假如 wait 先执行,notify 后执行, 如果每次都是这样, 线程是能够成功休眠, 也能成功的唤醒的.
但是假如 notify 先执行了,wait 后执行, 这时候就会进入永久等待

来看一个例子:

class BlockingQueue 
	Queue<String> buffer = new LinkedList<String>();
	public void give(String data) 
		buffer.add(data);
		notify();
	
	public String take() throws InterruptedException 
		while (buffer.isEmpty()) 
			wait();
		
		return buffer.remove();
	

上面是在非同步代码块中调用 wait 和 notify 的场景, 事实上上面的代码就是错误的, 此时假如能执行, 想象一下会发生什么?
上面是一个生产者/消费者模型, 调用 give 就往队列中加入元素, 调用 take 就从队列中取出来一个元素.
如果 buffer. isEmpty () 为 true, 那么就阻塞, 如果在多线程中, notify 先执行, 然后 wait 后执行, 这个时候, 如果后续没有 notify 执行, take ()方法就会永久阻塞住.

Sleep 为什么不定义在 Object 类中

嗯… 是个好问题, 没有必要, 线程的 Sleep 休眠状态和 Object 没太大的关系.

以上是关于关于wait,notify,notifyall,sleep方法的思考的主要内容,如果未能解决你的问题,请参考以下文章

多线程(四)wait()notify()以及notifyAll()

关于wait/notify

关于wait/notify

java condition await signal signalall对比wait notify notifyall

多线程-wait/notify/notifyAll

Java线程的wait(), notify()和notifyAll()