Java中的synchronized关键字全解析

 更新时间:2026年01月15日 08:46:17   作者:我会替风去  
synchronized是Java中用于解决并发问题的核心关键字,通过确保多个线程对共享资源的互斥访问,来避免线程安全问题,synchronized具有原子性、可见性、有序性和可重入性等特性,本文给大家介绍Java中的synchronized关键字相关,感兴趣的朋友跟随小编一起看看吧

synchronized是Java中用于解决并发问题的核心关键字,它通过确保多个线程对共享资源的互斥访问,来避免线程安全问题(如竞态条件、数据不一致等)。

synchronized的核心特性

  • 原子性(Atomicity):确保一个或多个操作要么全部执行成功,要么全部执行失败。在synchronized代码块中的代码是不可中断的,同一时刻只有一个线程能执行。
  • 可见性(Visibility):保证一个线程对共享变量的修改,对于其他后续进入同步代码块的线程是可见的。当线程释放锁时,会将私有内存中的变量值刷新回主内存。
  • 有序性(Ordering):虽然编译器和处理器为了优化会进行指令重排,但synchronized可以保证多线程程序在逻辑上的执行顺序,即“同步块内的代码在执行上具有先后顺序”。
  • 可重入性:可重入性是指一个线程已经获取到某个锁后,再次请求该锁时可以直接获取,无需重新竞争。synchronized是可重入锁,其内部通过计数器记录锁的持有次数(初始为0,获取锁时加1,释放锁时减1,计数器为0时锁才被真正释放)。这一特性避免了线程在递归调用同步方法/代码块时出现死锁。例如,一个同步方法A调用另一个同步方法B(两者锁对象相同),线程获取A的锁后,调用B时可直接获取锁。

synchronized的使用方式

synchronized的使用灵活,可修饰不同的代码结构,核心是明确“锁对象”——线程竞争的是锁对象,只有获取到锁对象的线程才能执行同步代码,常见使用方式有3种。

  • 修饰实例方法(对象锁),语法:public synchronized void methodName() { ... }
  • 锁对象:当前类的实例对象(this)。
  • 特点:不同实例对象的锁相互独立,即多个线程访问同一个实例的同步实例方法时会竞争锁;访问不同实例的同步实例方法时,因锁对象不同,不会竞争。
  • 示例:同一User实例的add()方法被多线程调用时互斥,不同User实例的add()方法可并行执行。
  • 修饰静态方法(类锁)语法:public static synchronized void methodName() { ... }
  • 锁对象:当前类的Class对象(每个类在JVM中只有一个Class对象,是全局唯一的)。
  • 特点:类锁是全局锁,无论创建多少个类的实例,所有线程访问该类的同步静态方法时,都会竞争同一个Class对象锁。
  • 注意:类锁与对象锁相互独立,即同步静态方法和同步实例方法的锁对象不同,线程访问时不会竞争。
  • 修饰代码块(自定义锁对象),语法:synchronized (锁对象) { ... 同步代码 ... }
  • 锁对象:可自定义,支持两种类型:① 实例对象(this或其他实例);② Class对象(类名.class)。
  • 特点:粒度最细,可精准控制需要同步的代码片段(而非整个方法),减少锁竞争,提高程序性能。
  • 常见场景:
    • 锁当前实例:synchronized (this) { ... },效果与修饰实例方法一致,但仅同步代码块内的逻辑。
    • 锁Class对象:synchronized (User.class) { ... },效果与修饰静态方法一致。
    • 锁自定义对象:private Object lock = new Object(); synchronized (lock) { ... },通过独立的锁对象,避免与其他同步逻辑竞争锁,灵活性最高。

synchronized的锁机制

  • Java 6及以后对synchronized进行了大幅优化,引入了“偏向锁、轻量级锁、重量级锁”三种锁状态,目的是根据线程竞争的激烈程度动态切换锁状态,平衡性能与线程安全。锁机制的核心是“对象头”——Java对象在内存中的布局包括对象头、实例数据、对齐填充,其中对象头存储了锁的状态信息(Mark Word)、类元数据指针等。
  • 三种锁状态的优先级:无锁 < 偏向锁 < 轻量级锁 < 重量级锁,随着线程竞争的加剧,锁会从低级别向高级别升级,且升级过程不可逆(一旦升级为重量级锁,无法回退为轻量级锁或偏向锁)。
    • 无锁(No Lock):初始状态。
    • 偏向锁(Biased Lock):当只有一个线程访问同步块时,直接在对象头记录线程ID,下次该线程进入时无需CAS操作,性能极高。
    • 轻量级锁(Lightweight Lock):当出现竞争,但竞争不激烈时,通过CAS自旋来尝试获取锁,避免线程阻塞。
    • 重量级锁(Heavyweight Lock):当自旋超过一定次数或竞争非常激烈时,升级为重量级锁。此时未获取到锁的线程会进入阻塞(Blocked)状态,交给操作系统管理。

锁优化补充

  • 锁消除:JVM的即时编译器(JIT)在运行时,会对一些“不可能存在竞争的锁”进行消除。例如,局部变量作为锁对象(每个线程都有独立的局部变量,无共享),JVM会直接删除该synchronized修饰,避免不必要的锁开销。
  • 锁粗化:当多个连续的synchronized代码块使用同一个锁对象时,JVM会将这些代码块合并为一个大的同步代码块,减少锁的获取/释放次数(每次获取/释放锁都有开销)。例如,循环内多次调用同步方法,JVM可能将锁粗化到循环外部。

CAS (Compare And Swap,比较并交换) 是并发编程中实现原子操作的核心算法,是一种乐观锁的实现策略。

  • CAS的工作原理,包含三个核心参数:
  • 内存地址 V (Memory Location):变量在内存中的实际值。
  • 期望值 A (Expected Value):线程认为该变量当前应该是什么值。
  • 新值 B (New Value):线程想要更新成的值。
  • 执行逻辑:当且仅当内存地址 V 的值等于期望值 A 时,处理器才会将 V 的值更新为 B。否则,说明该变量已被其他线程修改,当前线程什么都不做,通常会进入自旋(死循环重试)。
  • CAS的优缺点
    优点:
  • 非阻塞性:CAS 是一种非阻塞算法(Non-blocking),它不需要像 synchronized 那样挂起和恢复线程。
  • 性能高:在低、中度竞争的情况下,由于减少了线程上下文切换的开销,效率远高于重量级锁。
    缺点:
  • 循环时间长(自旋开销):如果高并发下竞争激烈,CAS 会频繁失败并不断自旋,这会给 CPU 带来巨大的计算压力。
  • 只能保证一个共享变量的原子操作:对于多个变量的操作,仍需使用 synchronized 或 ReentrantLock。
  • ABA 问题(最经典的缺点)。
  • 什么是 ABA 问题?
  • 如果变量初始值为A,在线程1准备修改它的过程中,线程2快速地将其改成了B,然后又改回了A。 现象:线程1观察到值依然是A,认为它没变过,于是CAS成功。 风险:虽然数值没变,但变量的状态(或对象内部的属性)可能已经发生了变化,导致逻辑错误。
  • Java提供了AtomicStampedReference类,通过引入版本号(Stamp)来解决: 每次变量更新时,不仅更新值,还增加一个版本号。只有值和版本号都一致,CAS才会成功。
  • 还可以设置时间戳来解决。
  • CAS 在Java中的实现
  • 在Java中,CAS主要由 sun.misc.Unsafe 类提供支持。该类中的方法(如 compareAndSwapInt)是 native 的,直接调用硬件底层的指令。

ReentrantLock(可重入锁)

  • ReentrantLock是 Java java.util.concurrent.locks 包下的可重入锁实现,基于 AQS(抽象队列同步器)构建,是 synchronized 的 “增强版”—— 既保留了 synchronized 的可重入特性,又提供了更灵活的同步控制能力。

两者对比:

特性synchronizedReentrantLock
实现层面JVM 层面(关键字),由 C++ 实现JDK 层面(API),由 Java 编写(基于 AQS)
锁的释放自动释放(代码执行完或异常后)手动释放(必须在 finally 中调用 unlock())
灵活性低(不可中断,无超时机制)高(支持尝试获取、超时获取、可中断获取)
公平性只支持非公平锁支持公平锁与非公平锁(默认非公平)
等待队列只能关联 1 个 等待队列(wait/notify)可以绑定 多个 Condition(精细化唤醒)

ReentrantLock的特有高级功能

  • 响应中断 (lockInterruptibly)
  • synchronized一旦进入阻塞等待,除非拿到锁,否则无法被中断。而ReentrantLock允许线程在等待锁的过程中响应Thread.interrupt(),从而避免死等。
  • 超时机制 (tryLock)
  • 线程可以尝试获取锁,如果锁被占用,立即返回 false 或者等待一段时间后返回,而不是一直阻塞。这在预防死锁时非常有用。
  • 公平锁 (Fairness)
  • 公平锁:按照线程请求锁的顺序分配,先到先得。
  • 非公平锁(默认):允许“插队”。如果新来的线程正好碰到锁释放,它可以直接抢占,性能通常比公平锁高。
  • 多个 Condition 对象
  • 通过lock.newCondition(),你可以创建多个等待集。例如在阻塞队列中,可以定义 notFull 和 notEmpty 两个条件,实现比 notifyAll 更精准的线程唤醒。

基本使用示例:

import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
    // 创建非公平锁(默认),若需公平锁:new ReentrantLock(true)
    private static final ReentrantLock lock = new ReentrantLock();
    public static void doTask() {
        // 1. 普通获取锁(不可中断)
        lock.lock();
        try {
            // 临界区代码(线程安全)
            System.out.println(Thread.currentThread().getName() + " 执行任务");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 必须在finally中释放锁,否则锁永远无法释放
            lock.unlock();
        }
    }
    // 超时获取锁示例
    public static void tryLockWithTimeout() {
        try {
            // 尝试在2秒内获取锁,获取成功返回true,失败返回false
            if (lock.tryLock(2, java.util.concurrent.TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 超时获取锁成功");
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " 超时获取锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    public static void main(String[] args) {
        // 测试普通获取锁
        new Thread(ReentrantLockDemo::doTask, "线程1").start();
        new Thread(ReentrantLockDemo::doTask, "线程2").start();
        // 测试超时获取锁
        new Thread(ReentrantLockDemo::tryLockWithTimeout, "线程3").start();
    }
}

到此这篇关于Java中的synchronized关键字全解析的文章就介绍到这了,更多相关java synchronized关键字内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • IntelliJ IDEA 2021.1 EAP 4 发布:字体粗细可调整Git commit template 支持

    IntelliJ IDEA 2021.1 EAP 4 发布:字体粗细可调整Git commit template 支持

    这篇文章主要介绍了IntelliJ IDEA 2021.1 EAP 4 发布:字体粗细可调整,Git commit template 支持,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • mybatis插件优雅实现字段加密的示例代码

    mybatis插件优雅实现字段加密的示例代码

    在很多时候,我们都需要字段加密,比如邮箱,密码,电话号码等,本文主要介绍了mybatis插件优雅实现字段加密的示例代码,感兴趣的可以了解一下
    2023-11-11
  • javascript fetch 用法讲解

    javascript fetch 用法讲解

    fetch 是一个现代化的 JavaScript API,用于发送网络请求并获取资源,它是浏览器提供的全局方法,可以替代传统的 XMLHttpRequest,这篇文章主要介绍了javascript fetch 用法讲解,需要的朋友可以参考下
    2025-05-05
  • Jlabel实现内容自动换行简单实例

    Jlabel实现内容自动换行简单实例

    这篇文章主要介绍了Jlabel实现内容自动换行简单实例,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Jmeter入门教程

    Jmeter入门教程

    jmeter是一款优秀的开源性能测试工具,目前最新版本3.0版本,本文给大家介绍Jmeter入门教程,文中通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-11-11
  • 详解eclipse将项目打包成jar文件的两种方法及问题解决方法

    详解eclipse将项目打包成jar文件的两种方法及问题解决方法

    本文给大家介绍了eclipse中将项目打包成jar文件的两种方法及其遇到问题解决方法,本文图文并茂给大家介绍的非常详细,需要的朋友可以参考下
    2017-12-12
  • 引入mybatis-plus报 Invalid bound statement错误问题的解决方法

    引入mybatis-plus报 Invalid bound statement错误问题的解决方法

    这篇文章主要介绍了引入mybatis-plus报 Invalid bound statement错误问题的解决方法,需要的朋友可以参考下
    2020-05-05
  • 什么是Base64以及在Java中如何使用Base64编码

    什么是Base64以及在Java中如何使用Base64编码

    这篇文章主要介绍了什么是Base64以及在Java中如何使用Base64编码的相关资料,文中包括Base64编码的基本原理、应用、Java实现、URL安全编码以及在JavaScript中的使用方法,需要的朋友可以参考下
    2024-12-12
  • Java基础之类型封装器示例

    Java基础之类型封装器示例

    这篇文章主要介绍了Java基础之类型封装器,结合实例形式分析了java类型封装相关原理与操作技巧,需要的朋友可以参考下
    2019-08-08
  • Java中的static关键字修饰属性和方法(推荐)

    Java中的static关键字修饰属性和方法(推荐)

    这篇文章主要介绍了Java中的static关键字修饰属性和方法,包括哪些成员属性可以被static修饰,静态属性的访问方法示例详解,需要的朋友可以参考下
    2022-04-04

最新评论