synchronized

一、synchronized 的特性

synchronized 是 Java 语言内置的关键字 ,用于实现线程同步 ,保障多线程环境下对共享资源的安全访问。它是 Java 最基础、最常用的线程安全机制之一。synchronized 是一个可重入、非公平、互斥 的锁。它通过偏向锁 优化单线程访问,通过轻量级锁/自旋 优化多线程交替访问,最后在竞争激烈时升级为重量级锁 。同时,它天然具备原子性、可见性有序性,是保障线程安全的"三驾马车"。

(一)synchronized 的核心行为特性

  • 互斥性 (Mutual Exclusion): 这是 synchronized 最根本的特性。它保证了同一时刻,只有一个线程能够获取到锁并执行临界区代码。当一个线程获取到锁后,其他试图获取同一把锁的线程会被阻塞,直到持有锁的线程释放锁。这确保了对共享资源的串行访问,解决了线程安全问题。

  • 可重入性 (Reentrant): synchronized 是可重入锁。这意味着同一个线程在已经持有锁的情况下,可以再次成功获取该锁而不会造成死锁。JVM 会记录锁的持有线程 ID 和一个计数器。当线程再次进入时,计数器递增;每次退出同步块时,计数器递减。只有当计数器归零时,锁才会真正被释放,其他线程才能获取。避免了"自己把自己锁死"的情况,也支持在 synchronized 方法中调用其他 synchronized 方法。

  • 内存可见性 (Visibility): synchronized 能够保证在释放锁之前,线程对共享变量的修改会刷新回主内存;而在获取锁之后,线程会从主内存重新读取最新的变量副本。这保证了多个线程之间共享变量的可见性,即一个线程修改了数据,其他线程能立即看到。

(二)锁机制与优化特性

  • 锁升级机制 (Lock Escalation): synchronized 的锁状态会随着竞争激烈程度动态升级,且升级过程不可逆
  1. 无锁状态

  2. 偏向锁 (Biased Locking): 初始状态。如果锁对象只有一个线程访问,JVM 会将锁"偏向"该线程,线程再次进入时不需要进行任何同步操作(CAS),直接通过检查对象头中的线程 ID 即可。

  3. 轻量级锁 (Lightweight Locking): 当有多个线程交替访问(无竞争或竞争不激烈)时,JVM 使用 CAS 操作来尝试获取锁,避免了操作系统层面的线程阻塞和唤醒,提高了性能。

  4. 重量级锁 (Heavyweight Locking): 当竞争激烈(如线程自旋超过一定次数仍未获取到锁),锁会膨胀为重量级锁。此时依赖操作系统的互斥量(Mutex)来实现,线程会进入阻塞状态,需要操作系统内核进行调度,开销最大。

  • **自旋锁策略 (Spin Lock):**在轻量级锁状态下,如果线程获取锁失败,它不会立即挂起(阻塞),而是会执行一段忙循环(自旋),看持有锁的线程是否很快就会释放。这是一种"以时间换空间"的策略,避免了线程切换的开销。

  • 非公平锁: synchronized 是非公平锁。这意味着当锁被释放时,等待时间最长的线程并不一定能优先获取到锁。新来的线程和队列中等待的线程有同等的概率竞争到锁,这可能导致某些线程长时间无法获取锁(饥饿),但整体吞吐量通常比公平锁更高。

二、synchronized的使用

synchronized 的本质是加锁,但锁的对象会根据使用位置的不同而变化。

(一)修饰实例方法 (对象锁) :当你用 synchronized 修饰一个普通方法时,锁的是当前实例对象(this。适用场景: 保护该实例对象的成员变量,防止多个线程同时修改同一个对象的状态。

java 复制代码
public class Counter {
    private int count = 0;

    // 锁住的是调用这个方法的实例对象 (this)
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

(二)修饰静态方法 (类锁): 当你用 synchronized 修饰一个静态方法时,锁的是该类的 Class 对象(类名.class。适用场景为保护静态变量(类变量),因为静态变量属于类,不属于某个具体实例。

java 复制代码
public class Counter {
    private static int totalCount = 0;

    // 锁住的是 Counter.class 对象
    public static synchronized void incrementTotal() {
        totalCount++;
    }
}

**(三)修饰代码块(同步代码块):**这是粒度最细、最灵活的用法。你可以指定任意对象作为锁。适用场景: 只需要同步方法中的一部分代码(临界区),以减少锁的持有时间,提高并发性能。

java 复制代码
public class Counter {
    private int count = 0;
    private final Object lock = new Object(); // 专用锁对象

    public void increment() {
        // 只锁住必要的代码块,而不是整个方法
        synchronized (lock) {
            count++;
        }
        // 这里可以执行不需要同步的耗时操作,不阻塞其他线程
    }
}

三、synchronized的锁机制

Java 中 synchronized 的锁机制是一个非常精妙的设计,它并不是一成不变的"重量级"锁,而是随着竞争激烈程度动态升级的。

(一)、核心组件

synchronized 的实现依赖于 JVM 对 Java 对象的管理和操作系统底层的互斥机制。

  • 对象头 (Object Header) 每个 Java 对象在内存中都有一个对象头,其中的 Mark Word 是核心。它在不同的锁状态下,存储的内容是不一样的(比如存储哈希码、线程 ID、指向锁记录的指针等)。
  • Monitor (监视器/管程) 这是 synchronized 实现同步的基础。每个 Java 对象都天生关联一个 Monitor。
  1. Owner: 指向持有该锁的线程。
  2. Entry Set: 存放等待获取锁的线程队列。
  3. Wait Set: 存放调用了 wait() 方法的线程队列。

(二)锁升级机制

这是 synchronized 性能优化的关键。为了减少线程阻塞和唤醒的开销,JVM 会根据竞争情况,从低级别的锁向高级别的锁自动升级。这个过程是不可逆的(只能升级,不能降级)。

  • 1. 无锁状态 (No Lock)

偏向锁 (Biased Locking) ------ [优化单线程]
  1. 场景: 大多数情况下,锁不仅没有竞争,而且总是由同一个线程多次获取。

  2. 机制: JVM 会把该锁"偏向"于当前线程。它会在对象头的 Mark Word 中记录该线程的 ID。

  3. 优点: 下次同一个线程再进入同步块时,不需要进行任何同步操作(不需要 CAS 操作),直接执行即可。加锁解锁几乎没有额外开销。

  4. 触发升级: 当有第二个线程尝试获取锁时,偏向锁就会失效,升级为轻量级锁。

轻量级锁 (Lightweight Locking) ------ [优化多线程交替]
  1. 场景: 线程间存在竞争,但不是特别激烈,且持有锁的时间很短。

  2. 机制:线程在自己的栈帧中建立一个锁记录 (Lock Record)。通过 CAS 操作,尝试将对象头的 Mark Word 指向这个锁记录。如果成功,获取锁;如果失败(说明有竞争),线程不会立即阻塞,而是进入自旋(Spin)状态,循环尝试获取锁。

  3. 优点: 避免了线程阻塞和唤醒的开销(用户态与内核态切换)。

  4. 缺点: 如果自旋次数过多(默认 10 次)或者竞争加剧,会消耗 CPU 资源。

重量级锁 (Heavyweight Locking) ------ [终极手段]
  1. 场景: 多线程竞争激烈,或者锁被持有时间很长。

  2. 机制: 当自旋超过一定阈值,或者等待线程过多时,锁会膨胀为重量级锁。

  3. 原理: 此时依赖操作系统的互斥量 (Mutex) 来实现。对象头的 Mark Word 指向一个重量级的 Monitor 对象。

  4. 表现: 所有未获取到锁的线程都会被阻塞(挂起),进入内核调度状态,不再消耗 CPU。只有当持有锁的线程释放锁后,操作系统才会唤醒阻塞队列中的一个线程。

  5. 缺点: 线程阻塞和唤醒涉及上下文切换,开销很大。

相关推荐
吃西瓜的年年8 小时前
5.C语言流程控制语句
c语言·开发语言
萧曵 丶8 小时前
Java 泛型详解
java·开发语言·泛型
码上宝藏8 小时前
从解耦到拓展:Clapper 0.10.0 插件化架构设计与 Lua 脚本集成
linux·开发语言·lua·视频播放器·clapper
qq_117179078 小时前
海康威视球机萤石云不在线问题解决方案
开发语言·智能路由器·php
superman超哥8 小时前
Rust 并发性能调优:线程、异步与无锁的深度优化
开发语言·后端·rust·线程·异步·无锁·rust并发性能
csbysj20208 小时前
Python 多线程
开发语言
superman超哥8 小时前
Rust Trait 对象与动态分发权衡:性能与灵活性的深度权衡
开发语言·后端·rust·rust trait·对象与动态发布·性能与灵活性
ftpeak8 小时前
Burn:纯 Rust 小 AI 引擎的嵌入式物体识别之旅(一步不踩坑)
开发语言·人工智能·rust
独断万古他化8 小时前
【Spring Web MVC 入门实战】实战三部曲由易到难:加法计算器 + 用户登录 + 留言板全流程实现
java·后端·spring·mvc
学后端的小萝卜头8 小时前
如何通过HTTP Range请求分段获取OSS资源(下载篇)
java·网络·http