首页IT科技wait和notify的原理(面试官:为什么 wait/notify 必须与 synchronized 一起使用??)

wait和notify的原理(面试官:为什么 wait/notify 必须与 synchronized 一起使用??)

时间2025-05-05 16:46:11分类IT科技浏览3358
导读:来源:blog.csdn.net/randompeople/article/details/114917087...

来源:blog.csdn.net/randompeople/article/details/114917087

为什么 java wait/notify 必须与 synchronized 一起使用

这个问题就是书本上没怎么讲解            ,就是告诉我们这样处理                ,但没有解释为什么这么处理?我也是基于这样的困惑去了解原因            。

synchronized是什么

Java中提供了两种实现同步的基础语义:synchronized方法和synchronized块     , 看个demo:

public class SyncTest { \\ 1          、synchronized方法 public synchronized void syncMethod(){ System.out.println("hello method"); } \\ 2                 、synchronized块 public void syncBlock(){ synchronized (this){ System.out.println("hello block"); } } }

具体还要区分:

修饰实例方法         ,作用于当前实例加锁                 ,进入同步代码前要获得当前实例的锁                。不同实例对象的访问       ,是不会形成锁的     。 修饰静态方法      ,作用于当前类对象加锁                  ,进入同步代码前要获得当前类对象的锁 修饰代码块          ,指定加锁对象   ,对给定对象加锁                  ,进入同步代码库前要获得给定对象的锁         。

它具有的特性:

原子性 可见性 有序性 可重入性

synchronized如何实现锁

这样看来synchronized实现的锁是基于class对象来实现的             ,我们来看看如何实现的,它其实是跟class对象的对象头一起起作用的               ,对象在内存中的布局分为三块区域:对象头      、实例数据和对齐填充                 。

其中对象头中有一个Mark Word                ,这里主要存储对象的hashCode        、锁信息或分代年龄或GC标志等信息   ,把可能的情况列出来大概如下:

其中synchronized就与锁标志位一起作用实现锁       。主要分析一下重量级锁也就是通常说synchronized的对象锁            ,锁标识位为10                ,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址      。

每个对象都存在着一个 monitor 与之关联     ,对象与其 monitor 之间的关系有存在多种实现方式         ,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成                 ,但当一个 monitor 被某个线程持有后       ,它便处于锁定状态                  。

在Java虚拟机(HotSpot)中      ,monitor是由ObjectMonitor实现的                  ,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件          ,C++实现的):

ObjectMonitor() { _header = NULL; _count = 0; //记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //处于wait状态的线程   ,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //处于等待锁block状态的线程                  ,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }

上面有2个字段很重要:

_WaitSet队列处于wait状态的线程             ,会被加入到_WaitSet          。 _EntryList队列处于等待锁block状态的线程,会被加入到该列表   。 _owner_owner指向持有ObjectMonitor对象的线程

我们来模拟一下进入锁的流程:

1                、当多个线程同时访问一段同步代码时               ,首先会进入 _EntryList 集合

2         、当线程获取到对象的monitor 后进入 _Owner 区域                ,并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1

3      、若线程调用 wait() 方法   ,将释放当前持有的monitor            ,owner变量恢复为null                ,count自减1     ,同时该线程进入 WaitSet集合中等待被唤醒                  。

4                、若当前线程执行完毕也将释放monitor(锁)并复位变量的值         ,以便其他线程进入获取monitor(锁)

wait/notify

这两个是Java对象都有的属性                 ,表示这个对象的一个等待和通知机制             。

推荐一个开源免费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

不用synchronized 会怎么样

参考其他博客       ,我们来看看不使用synchronized会怎么样      ,假设有2个线程                  ,分别做2件事情          ,T1线程代码逻辑:

while(!条件满足) // line 1 { obj.wait(); // line 2 } doSomething();

T2线程的代码逻辑:

更改条件为满足; // line 1 obj.notify(); // line 2

多线程环境下没有synchronized   ,没有锁的情况下可能会出现如下执行顺序情况:

T1 line1 满足while 条件 T2 line1 执行 T2 line2 执行                  ,notify发出去了 T1 line2 执行             ,wait再执行

这样的执行顺序导致了notify通知发出去了,但没有用               ,已经wait是在之后执行                ,所以有人说没有保证原子性   ,就是line1 和line2 是一起执行结束            ,这个也被称作lost wake up问题。解决方法就是可以利用synchronized来加锁                ,于是有人就写了这样的代码:

synchronized(lock) { while(!条件满足) { obj.wait(); } doSomething(); } synchronized(lock) { 更改条件为满足; obj.notify(); }

这样靠锁来做达到目的               。但这代码会造成死锁     ,因为先T1 wait()         ,再T2 notify();而问题在于T1持有lock后block住了                 ,T2一直无法获得lock       ,从而永无可能notify()并将T1的block状态解除      ,就与T1形成了死锁                。

所以JVM在实现wait()方法时                  ,一定需要先隐式的释放lock          ,再block   ,并且被notify()后从wait()方法返回前                  ,隐式的重新获得了lock后才能继续user code的执行   。要做到这点             ,就需要提供lock引用给obj.wait()方法,否则obj.wait()不知道该隐形释放哪个lock               ,于是调整之后的结果如下:

synchronized(lock) { while(!条件满足) { obj.wait(lock); // obj.wait(lock)伪实现 // [1] unlock(lock) // [2] block住自己                ,等待notify() // [3] 已被notify()   ,重新lock(lock) // [4] obj.wait(lock)方法成功返回 } doSomething(); }

[最终形态] 把lock和obj合一

其它线程API如PThread提供wait()函数的签名是类似cond_wait(obj, lock)的            ,因为同一个lock可以管多个obj条件队列            。而Java内置的锁与条件队列的关系是1:1                ,所以就直接把obj当成lock来用了                。因此此处就不需要额外提供lock     ,而直接使用obj即可         ,代码也更简洁:

synchronized(obj) { while(!条件满足) { obj.wait(); } doSomething(); } synchronized(lock) { 更改条件为满足; obj.notify(); }

lost wake up

wait/notify 如果不跟synchronized结合就会造成lost wake up                 ,难以唤醒wait的线程       ,所以单独使用会有问题     。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了         。                 。       。

3.Spring Boot 2.x 教程      ,太全了!

4.别再写满屏的爆爆爆炸类了                  ,试试装饰器模式          ,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布   ,速速下载!

觉得不错                  ,别忘了随手点赞+转发哦!

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
帝国CMS7.5 避免漏洞(帝国CMS 7.0商城系统常见问题与技巧教程分享)