当前位置: 首页 > news >正文

synchronized

概述

synchronized 是 Java 语言中的关键字,是一种内置锁,用于实现线程的同步。它的主要目的是确保多个线程在同一时刻,只能有一个线程可以进入被 synchronized 修饰的代码段或方法


一、用法

synchronized 主要有三种使用方式。

1. 同步实例方法

锁是当前实例对象

public class Counter {private int count = 0;// 同步实例方法,锁是 this,即当前Counter对象实例public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}

当一个线程调用 counter1.increment() 时,它会获取 counter1 这个对象实例的锁。其他线程要调用 counter1 的任何一个同步实例方法(如 getCount())都会被阻塞。但是,如果另一个线程操作的是 不同的实例(如 counter2),则不会受影响,因为它们获取的是不同对象的锁。

2. 同步静态方法

锁是当前类的 Class 对象

public class StaticCounter {private static int count = 0;// 同步静态方法,锁是 StaticCounter.classpublic static synchronized void increment() {count++;}
}

因为静态方法属于类而不属于任何实例,所以锁是类的 Class 对象。这意味着,即使有多个 StaticCounter 的实例,所有线程在执行任何一个同步静态方法时,都需要竞争同一把锁

3. 同步代码块

可以更细粒度地控制锁的范围和锁的对象,提升并发效率。

  • 指定对象作为锁

    public class FineGrainedCounter {private int count = 0;private final Object lock = new Object(); // 专门用于锁的对象public void increment() {// 同步代码块,锁是 lock 对象synchronized (lock) {count++;}// 其他不需要同步的代码可以放在这里,减少锁的持有时间}
    }
    

    使用一个专门的私有锁对象是一个好习惯,可以防止外部代码意外获取到你的锁,导致死锁或其他并发问题。

  • 使用 this

    public void method() {// 同步代码块,锁是当前实例对象,等同于同步实例方法的一部分synchronized (this) {// ...}
    }
    
  • 使用 .class

    public void staticMethod() {// 同步代码块,锁是类的Class对象,等同于同步静态方法的一部分synchronized (FineGrainedCounter.class) {// ...}
    }
    

二、原理

synchronized 的实现原理涉及到 Java 对象头Monitor(监视器)

1. Java 对象头

在 JVM 中,每个对象在内存中分为三部分:

  1. 对象头
  2. 实例数据
  3. 对齐填充

其中,对象头synchronized 实现锁的关键。它主要包含两部分:

  • Mark Word:存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、持有锁的线程 ID 等。
  • Klass Pointer:对象指向它的类元数据的指针。
2. Monitor(监视器锁)

每一个 Java 对象都与一个 Monitor 相关联。可以把 Monitor 理解为一个特殊的、线程私有的数据结构。当线程执行到 synchronized 保护的代码时,其执行流程如下:

  1. 进入同步代码块:线程尝试通过对象的对象头获取与之关联的 Monitor。
  2. 获取锁
    • 如果 Monitor 的计数器为 0(表示未被任何线程持有),当前线程会成功获取该 Monitor,并将计数器设置为 1。此时,该线程是 Monitor 的持有者。
    • 如果当前线程已经持有该 Monitor,它可以重入,计数器会再次加 1(这就是 synchronized 是可重入锁的原因)。
    • 如果 Monitor 被其他线程持有,当前线程会被阻塞,进入 同步队列,状态变为 BLOCKED
  3. 执行代码:获取锁成功后,线程执行同步代码块内的代码。
  4. 退出同步代码块:当线程执行完同步代码块,或者抛出异常时,它会释放 Monitor,将计数器减 1。
    • 如果计数器减为 0,表示线程完全释放了锁。
    • 然后,它会唤醒在同步队列中等待的线程,让它们重新竞争锁。

这个“竞争”过程是由 JVM 和操作系统底层协作完成的,是非公平的。

3. 锁的升级过程

在这里插入图片描述
JVM 通过读取并解析锁对象的 Mark Word 中的锁标志位和偏向锁标志位,来精确判断锁的当前状态,从而决定是直接获取锁、升级锁、还是进入自旋/阻塞。

为了在性能和开销之间取得平衡,Java 6 之后,synchronized 的锁状态是可以升级的,而不是一成不变的重量级锁。这个过程是单向的。

锁的级别从低到高依次为:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

  • 偏向锁

    • 场景:假设在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。
    • 原理:当一个线程访问同步块时,会在对象头和栈帧中的锁记录里存储偏向的线程 ID。以后该线程再进入和退出同步块时,不需要进行 CAS 操作来加锁和解锁,只需简单测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁。
    • 目的:消除在无竞争情况下的同步开销。
  • 轻量级锁

    • 场景:当偏向锁被另一个线程访问时(发生竞争),偏向锁会升级为轻量级锁。
    • 原理:线程在执行同步块之前,JVM 会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中。然后线程尝试使用 CAS 操作将对象头中的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁;如果失败,表示其他线程竞争锁,当前线程会通过自旋来尝试获取锁。
    • 目的:在响应速度和高吞吐量之间取得平衡。
  • 重量级锁

    • 场景:如果轻量级锁自旋失败(比如自旋次数超过阈值,或者有多个线程在竞争),锁就会膨胀为重量级锁。
    • 原理:此时,Mark Word 中存储的是指向重量级锁(Monitor)的指针。等待锁的线程都会进入阻塞状态。(有了操作系统参与,当线程无法获取重量级锁时:线程调用 pthread_mutex_lock() 等系统调用,陷入内核态(通过软中断/系统调用门),内核检查锁状态:如果锁可用:立即获取,返回用户态,如果锁被占用:将线程放入等待队列,标记为阻塞状态
      调度器选择其他就绪线程运行)
    • 特点:开销最大,但能避免 CPU 空转。
http://www.dtcms.com/a/565956.html

相关文章:

  • 注册安全工程师考试题库免费发seo外链平台
  • [LitCTF 2023]作业管理系统
  • 重庆网网站建设公司seo营销是什么意思
  • RabbitMQ:仲裁队列 HAProxy
  • 推荐个在广州做网站的做的网站需要什么技术支持
  • 通用测试代码结构规范 - Cursor Rules
  • 软件测试基础
  • 厦门网站推广步骤机构网站建设什么原因最主要
  • 网站建设周期与进度安排神木网站设计公司
  • 单网卡同时上内外网设置
  • openwrt 做视频网站怎么在wordpress导航条下方加入文字广告链接
  • 简述营销型企业网站建设的内容建立个公司网站
  • 【深度学习入门】小土堆-学习笔记
  • 黑龙江省建设局网站辽宁建设工程信息网怎么无法登陆了
  • 好文与笔记分享 A Survey of Context Engineering for Large Language Models(下)
  • 【大模型】多模态大语言模型(MLLMs):架构演进、能力评估与应用拓展的全面解析
  • 跟der包学习java_day8「继承(Inheritance)」
  • 移动+协作+视觉:开箱即用的下一代复合机器人如何重塑智能工厂
  • C语言应用实例:斐波那契数列与其其他应用
  • 计网5.3.6 TCP拥塞控制
  • 模数转换器主要类型有哪些
  • 先到先得!深度学习有限元仿真!
  • 短信登录和登录校验(线程安全、ThreadLocal、进程调度)
  • 旅游网站html快速网站收录
  • 视频网站开发用什么服务器门户网站登录入口
  • python虚拟环境应用
  • SpringBoot-启动流程
  • 余姚公司建设网站海东高端网站建设价格
  • C# OpencvSharp使用lpd_yunet进行车牌检测
  • 淘宝联盟登记新网站广州网站建设公司怎么选