起源: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开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!
发表回复