Java - JVM - 监视器锁 与 等待队列

时间:2020-05-20
本文章向大家介绍Java - JVM - 监视器锁 与 等待队列,主要包括Java - JVM - 监视器锁 与 等待队列使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
  1. 概述

    1. jvm 监视器锁 与 等待队列
    2. 初版, 目前来看, 还是一个 生硬的总结
      1. 后续会做调整
  2. 背景

    1. 之前讲了 synchronized
    2. 但是其中的原理, 并没有讲
    3. 这些是定义在 java 内存模型 里的

1. 回顾: synchronized

  1. 概述

    1. 回顾之前的内容
  2. 格式

    1. 方法

      # 后面简称 同步方法
      public static synchronized void method() {}
      public synchronized void method() {}
      
    2. 代码块

      # 后面简称 同步代码块
      synchronized(obj) {}
      
  3. 作用

    1. 通过一个对象, 来限定 synchronized 范围内的代码块
      1. 获取对象锁的线程, 可以执行代码块内容
      2. 其他线程, 需要等到 对象锁 被释放, 才有机会执行
  4. 所以

    1. 这个锁, 到底是个 什么情况
      1. 监视器锁

2. 监视器锁

  1. 概述

    1. 监视器锁
  2. 监视器锁

    1. 概述

      1. 一种 同步机制
    2. 机制

      1. 对象

        1. 每个对象, 都有一个 监视器锁
      2. 线程

        1. 持有

          1. 线程获取 监视器锁 成功, 则称 线程持有锁
            1. 同时, 相关的 同步方法/代码块, 进入 加锁状态, 只有 持有锁 的线程, 才可以执行
          2. 同一时间, 同一个监视器锁, 只能被一个线程持有
            1. 同一个线程, 可以持有同一个 监视器锁 多次 - 递归
            2. 同一个线程, 可以持有不同的 监视器锁 多次 - 同步方法/同步代码块 的连环调用
          3. 持有 监视器锁
            1. 可以执行执行 依赖该锁 的 同步方法/同步代码块
        2. 触发

          1. 线程执行 同步代码块/同步方法 时, 会 触发 对 相应监视器锁 的请求
        3. 请求

          1. 未加锁
            1. 单线程请求
              1. 线程请求成功, 持有锁
              2. 相关 同步方法/同步代码块 加锁
            2. 多线程请求
              1. 只有一个线程 可以请求成功, 并持有锁
              2. 相关 同步方法/同步代码块 加锁
              3. 其他线程, 进入 阻塞状态
                1. 直到 之前的线程, 不再持有锁, 展开下一次 竞争
                2. 老实说, 这个状态, 我也不太清楚, 先这么说, 不影响理解
          2. 已加锁
            1. 持有锁线程
              1. 再额外持有一次锁
            2. 其他线程
              1. 进入 阻塞状态
          3. 锁对象
            1. 实例方法 和 普通代码块
              1. 申请 对象 的监视器锁
            2. 静态方法
              1. 申请 类对象 的监视器锁
        4. 释放

          1. 正常
            1. 情况
              1. 方法正常执行完
            2. 结果
              1. 持有锁 的线程, 归还 一层锁
                1. 释放的顺序, 类似 栈 - 先持有, 后释放
              2. 如果 线程持有 0 层锁, 则 同步方法/同步代码块 不再加锁
          2. 异常
            1. 情况
              1. 出现 未处理异常 时, 会抛出异常 并 归还 所有锁
                1. 这个在 ref 里有提到, 感兴趣的朋友, 可以看下
  3. 问题

    1. 现状
      1. 已经形成一个相对完整的循环

        # 正常情况
        触发 > 请求 > 执行 > 释放 > 再次请求
        
    2. 问题
      1. 线程的切换

        1. 每个线程, 必须执行完一段 同步方法/同步代码块, 才能切换
        2. 这样的切换方式, 感觉有些 机械, 无法应对一些场景
      2. 场景

        1. 线程A 在 同步代码/同步代码块 中, 需要等待另一个资源就绪
          1. 按现在的机制来设计代码 - 暂时不考虑 异步...
            1. 检测资源是否就绪
            2. 如果就绪了, 就正常执行
            3. 如果没有就绪, 则直接退出
            4. 退出之后, 由外面一层方法负责 轮询
      3. 问题

        1. 需要一个 循环检测
          1. 增加了代码的 复杂度
        2. 如果资源没有就绪, 则 需要重新执行方法
          1. 方法中 部分代码, 可能会执行多次,
          2. 需要考虑 幂等性
          3. 增加了代码的 复杂度
        3. 锁的影响
          1. 要么一直 持有锁, 不释放
            1. 如果别的线程也需要这样的锁, 有概率在成 长时间阻塞
          2. 要么多次 申请同一个锁
            1. 如果这个锁竞争激烈, 可能会导致处理效率降低
            2. 竞争本身, 线程切换, 都会有资源的消耗
          3. 增加了 系统的额外消耗
      4. 解决

        1. 让线程之间, 相互协调
  4. 注意

    1. 同一个监视器锁, 可以被持有多次
      1. 解锁也需要多次
    2. 同一个线程, 可以持有多个监视器锁
    3. 持有多个锁的时候, 如果出现异常, 会一次把所有锁 都归还

3. 等待队列

  1. 概述

    1. 等待队列
  2. 准备

    1. 场景
      1. 线程
        1. 多个线程
      2. 同步方法/同步代码块
        1. 一段
  3. 机制

    1. 等待

      1. 前提
        1. 线程 持有锁
      2. 操作
        1. 线程执行 等待 操作
          1. LockObj.wait()
          2. LockObj.wait(time)
      3. 结果
        1. 线程 放弃锁
        2. 线程进入 等待队列
    2. 等待队列

      1. 监视器锁
        1. 每个 监视器锁 都有一个 等待队列
      2. 线程
        1. 进入等待队列的线程, 都是曾经 持有过锁, 并且主动放弃的
        2. 队列里的线程, 会一直呆在队列里, 不会再对锁去做 申请
          1. 除非被唤醒
    3. 唤醒

      1. 前提
        1. 线程 持有锁
      2. 操作
        1. 线程执行 唤醒 操作
          1. LockObj.notify()
          2. LockObj.notifyAll()
      3. 结果
        1. notify
          1. 随机一个线程被唤醒
        2. notifyAll
          1. 唤醒所有线程
        3. 被唤醒的线程
          1. 离开等待队列
          2. 重新参与 锁的竞争
          3. 抢到锁之后, 从 wait() 后面开始继续执行
  4. 疑问

    1. 等待队列里 唤醒的线程, 和 被阻塞 的线程, 优先级有区别吗

      1. 我目前没发现区别
        1. 可以理解为, 从 等待队列 里出来, 就重新去外面排队了
        2. 等待队列的作用, 就是让你 既没有锁, 也不去排队
    2. 为什么 wait 和 notify 都需要 持有锁呢

      1. 目的
        1. 防止 notify 被忽略
      2. 场景
        1. 线程 t1

          1. 执行 wait()

          2. 但是 wait 通常不单独存在, 需要配合 条件判定 来使用

          3. 所以, 通常是

            if (condition) {
                wait();
            }
            
        2. 线程 t2

          1. 执行 notifyAll()
        3. 假设, 都没有同步

        4. 执行

          1. t1: 判定通过, 还没来得及执行 wait(), 被切换了
          2. t2: 执行 notifyAll(), 切回去
          3. t1: 执行 wait(), 完美错过 notify
  5. 异常

    1. 如果 wait 中发生中断, 会抛出异常

      1. InterruptedException
    2. 处理

      1. 机制
        1. try...catch
      2. 机制
        1. 等待队列中的线程t, 接收到 中断请求
        2. 线程t 响应中断请求, 退出 等待队列, 并修改 自身状态
        3. 线程重新获得锁时, try...catch...内的处理, 会执行
          1. Java 语言规范

4. 后续

  1. 概述

    1. 感觉后续还有些问题
  2. 后续

    1. InterruptedException 的一些机制
      1. 触发时机
      2. 标记位
    2. 线程停止的其他方法
      1. sleep
      2. yeild
      3. join
    3. 线程状态
      1. 目前只有 运行, 阻塞, 等待

ps

  1. ref

    1. Java 语言规范(Se 8)
      1. chpt17
    2. 图解 Java 多线程设计模式
      1. 序章1
    3. 一个线程执行synchronized同步代码时,再次重入该锁过程中,如果抛出异常,会释放锁吗?
    4. Java SE 8 源码
      1. Object.java
    5. 关于 wait() 方法的同步执行
      1. 为什么WAIT必须在同步块中
      2. Why must wait() always be in synchronized block
    6. 关于 InterruptedException
      1. Java并发--InterruptedException机制
        1. 虽然标榜原创, 但是和 下面 2006 年的文章, 十分相似
        2. 几篇 ref 都值得一看
      2. 处理 InterruptedException
  2. 感觉

    1. 并发的内容, 需要先明确这么些东西
      1. 哪些线程, 会共用一把锁
      2. 这些线程, 会有什么样的 公共资源
      3. 线程之间, 需要怎么样的依赖关系

原文地址:https://www.cnblogs.com/xy14/p/12922999.html