乐趣区

关于java:面试官为什么-waitnotify-必须与-synchronized-一起使用

起源: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 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

退出移动版