侧边栏壁纸
博主头像
月伴飞鱼 博主等级

行动起来,活在当下

  • 累计撰写 126 篇文章
  • 累计创建 31 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Synchronized为啥需要维护一个计数器呢?独占锁一个状态表示就行了吧,计数的目的是啥?

月伴飞鱼
2025-04-28 / 0 评论 / 0 点赞 / 2 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

确实,通过记录线程 ID(或线程标识)可以判断一个锁是否被某个线程持有。

但维护计数器并不是为了简单判断锁的持有者,而是为了支持可重入锁的完整语义。

这是因为:

1. 可重入锁需要支持嵌套调用

假设只记录线程 ID,而不维护计数器,来看一个嵌套调用的场景:

示例代码

synchronized(obj) {
    // 第一次获取锁
    synchronized(obj) {
        // 第二次获取锁(嵌套)
    }
}
  • 第一次进入 synchronized(obj) 时,线程 ID 被记录,表示该线程持有锁。

  • 如果没有计数器,第二次进入 synchronized(obj) 时,程序需要检查当前线程是否与锁的持有者线程 ID 相同。

问题

  • 退出时的困惑

    • 当嵌套的第二层 synchronized(obj) 结束时,如果只根据线程 ID 而不维护计数器,程序如何知道此时锁还需要继续保持,还是应该释放锁?

2. 为什么计数器是必要的

计数器的作用

  • 记录线程获取锁的次数:确保锁在嵌套调用中不会过早释放。

  • 准确释放锁

    • 只有当获取锁的线程退出到最外层时(即计数器减为 0),锁才会真正释放。

    • 这样可以避免其他线程在中途抢占锁,导致线程安全问题。

示例代码

以下是维护计数器的正确行为:

  1. 第一次获取锁

    • 线程 ID 被记录。

    • 计数器从 0 增加到 1。

  2. 第二次获取锁(同一线程,嵌套调用):

    • 检查线程 ID 相同,计数器从 1 增加到 2。

  3. 释放锁

    • 嵌套调用结束时,计数器从 2 减少到 1。

    • 只有当计数器减为 0 时,锁才真正释放。

3. 不使用计数器的后果

如果只记录线程 ID 而不使用计数器:

  • 嵌套调用无法正确处理

    • 当嵌套调用退出时,锁可能被过早释放。

    • 例如,以下代码可能导致并发问题:

      synchronized(obj) {
          synchronized(obj) {
              // 第二次获取锁
          }
          // 此处锁可能已经被释放,但外层还在访问共享资源
      }
  • 锁释放的逻辑更复杂

    • 需要额外的机制来追踪嵌套调用的深度,计数器正是为此目的而设计的。

4. 实现中的细节

在 Java 的 synchronized 实现中,锁对象的状态信息中会记录以下内容:

  1. 持有线程 ID:标记当前锁的持有者。

  2. 计数器:记录当前线程获取锁的次数。

计数器在两方面发挥作用:

  • 锁的嵌套调用:允许同一线程多次获取锁。

  • 锁的释放条件:确保只有在最外层同步块退出时,锁才被真正释放。

5. 为什么计数器更直观

相比只记录线程 ID 的设计,计数器使锁的实现和语义更直观:

  • 计数器直接反映了锁的嵌套深度。

  • 计数器为线程释放锁提供了明确的条件(即计数器减为 0 时释放锁)。

总结

记录线程 ID 可以判断锁的持有者,但无法准确反映嵌套调用的深度,容易导致锁的过早释放问题。

计数器的设计是为了支持 可重入锁 的嵌套调用语义,同时确保锁在最外层调用退出时才会被正确释放。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
    1. 支付宝打赏

      qrcode alipay
    2. 微信打赏

      qrcode weixin
博主关闭了所有页面的评论