乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

synchronized 详解

场景:周五下午,飞翔科技的会议室里烟雾缭绕。大翔把一份线上事故报告摔在桌上:"我们的银行账户转账系统,昨天同一个账户被并发转了两次 5000 块,结果余额只扣了一次!"

白歌(表情严肃):"典型的线程安全问题。两个线程同时读到相同的余额,各自扣款后写回,后写的覆盖了先写的——这就是丢失更新。"

小崔(挠头):"那我加个 synchronized 不就行了?"

Frank:"It's not that simple. synchronized is the foundation, but you need to understand lock escalation, reentrancy, and the difference between class lock and instance lock. And in JDK 8, biased locking is enabled by default — that changes the game."

朱璐(在笔记本上快速翻看):"synchronized 从 JDK 1.6 开始引入了偏向锁、轻量级锁、重量级锁的升级机制。JDK 8 里,偏向锁默认延迟 4 秒启动——你知道吗?"

大翔:"那你从头给我们讲一遍吧。从 Mark Word 到锁升级,一个都别漏。"


一、定义速查表

属性说明
synchronizedJava 内置的互斥同步机制,保证同一时刻只有一个线程执行被保护的代码块。可修饰方法(实例/静态)和代码块
监视器锁(Monitor)每个 Java 对象都关联一个 Monitor,线程通过获取 Monitor 所有权进入 synchronized 区域。底层对应 monitorenter / monitorexit 字节码指令
对象头(Object Header)每个 Java 对象在堆内存中的头部区域,包含 Mark Word 和 Klass Pointer。Mark Word 存储锁状态、GC 年龄、hashCode 等
Mark Word对象头中的机器字,根据锁状态复用存储空间。32 位 JVM 占 32bit,64 位 JVM 占 64bit(JDK 8 默认使用 64 位 JVM)
偏向锁(Biased Locking)锁偏向于第一个获取它的线程,该线程再次进入无需 CAS 操作。JDK 8 默认开启,启动延迟 4 秒(-XX:BiasedLockingStartupDelay=0 可关闭延迟)
轻量级锁(Lightweight Locking)通过 CAS 自旋尝试获取锁,适用于锁竞争不激烈场景。在栈帧中创建 Lock Record,CAS 替换 Mark Word
重量级锁(Heavyweight Locking)依赖操作系统互斥量(Mutex),未获取锁的线程阻塞。涉及用户态↔内核态切换,性能开销大
锁升级(Lock Escalation)偏向锁 → 轻量级锁 → 重量级锁,单向不可逆。JDK 8 默认策略:先偏向,遇竞争再升级
可重入(Reentrant)同一线程可以多次获取同一把锁,不会死锁。synchronized 天生可重入,Monitor 内部维护重入计数器
锁粗化(Lock Coarsening)JIT 编译器将多个连续的加锁/解锁操作合并为一个大锁范围,减少加解锁开销
锁消除(Lock Elimination)JIT 编译器判定某段代码不可能被多线程访问时,直接移除 synchronized,通过逃逸分析实现
类锁 vs 实例锁synchronized 修饰静态方法锁定类的 Class 对象;修饰实例方法锁定当前实例 this;修饰代码块可指定任意锁对象

二、Mermaid 图解

2.1 锁升级状态图(偏向锁 → 轻量级锁 → 重量级锁)

2.2 Monitor 进入/退出流程图


三、synchronized 的三种用法

用法锁对象示例
同步实例方法this(当前实例对象)public synchronized void method() {}
同步静态方法类名.class(Class 对象)public static synchronized void method() {}
同步代码块任意指定对象synchronized(lockObj) { ... }

关键区别:

  • 实例方法锁 → 同一实例上的所有 synchronized 实例方法互斥
  • 静态方法锁 → 整个类的所有 synchronized 静态方法互斥(全局锁)
  • 实例锁和类锁相互独立,互不影响

四、完整代码示例

示例 1:飞翔科技银行账户并发转账 —— synchronized 方法保护

场景:大翔的工资卡(账号 10001)余额为 8888.88f,大翔在 ATM 取款,同时小崔用网银转出。如果不加锁,并发操作会导致余额错误。

/**
 * 飞翔科技 - 银行账户并发转账安全问题演示
 * 
 * 场景:账号 10001,初始余额 8888.88 元
 *       大翔到 ATM 取款,小崔用网银转账同时发生
 */
public class BankTransferDemo {

    // === 不安全版本:无同步保护 ===
    static class UnsafeAccount {
        private String owner;
        private float balance;  // 余额

        public UnsafeAccount(String owner, float balance) {
            this.owner = owner;
            this.balance = balance;
        }

        // ✗ 没有 synchronized — 多线程并发调用会丢失更新
        public void withdraw(float amount) {
            if (balance >= amount) {
                // 模拟网络延迟
                try { Thread.sleep(10); } catch (InterruptedException e) { }
                balance -= amount;
                System.out.printf("[%s] 取款 %.2f 成功, 余额: %.2f\n",
                        Thread.currentThread().getName(), amount, balance);
            } else {
                System.out.printf("[%s] 余额不足! 当前余额: %.2f\n",
                        Thread.currentThread().getName(), balance);
            }
        }

        public float getBalance() { return balance; }
    }

    // === 安全版本:synchronized 实例方法 ===
    static class SafeAccount {
        private String owner;
        private float balance;

        public SafeAccount(String owner, float balance) {
            this.owner = owner;
            this.balance = balance;
        }

        // ✓ synchronized 实例方法 — 锁是当前对象 this
        public synchronized void withdraw(float amount) {
            if (balance >= amount) {
                try { Thread.sleep(10); } catch (InterruptedException e) { }
                balance -= amount;
                System.out.printf("[%s] 取款 %.2f 成功, 余额: %.2f\n",
                        Thread.currentThread().getName(), amount, balance);
            } else {
                System.out.printf("[%s] 余额不足! 当前余额: %.2f\n",
                        Thread.currentThread().getName(), balance);
            }
        }

        public synchronized float getBalance() { return balance; }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 飞翔科技银行账户安全测试 ==========\n");

        // === 测试不安全版本 ===
        System.out.println("--- 不安全版本(无 synchronized)---");
        UnsafeAccount unsafeAcc = new UnsafeAccount("大翔工资卡(10001)", 8888.88f);
        Thread u1 = new Thread(() -> {
            for (int i = 0; i < 8; i++) unsafeAcc.withdraw(555.55f);
        }, "大翔-ATM");
        Thread u2 = new Thread(() -> {
            for (int i = 0; i < 8; i++) unsafeAcc.withdraw(555.55f);
        }, "小崔-网银");
        u1.start(); u2.start();
        u1.join(); u2.join();
        System.out.printf(">>> 不安全版本最终余额: %.2f (期望: %.2f)\n\n",
                unsafeAcc.getBalance(), 8888.88f - 16 * 555.55f);

        // === 测试安全版本 ===
        System.out.println("--- 安全版本(synchronized 方法)---");
        SafeAccount safeAcc = new SafeAccount("大翔工资卡(10001)", 8888.88f);
        Thread s1 = new Thread(() -> {
            for (int i = 0; i < 8; i++) safeAcc.withdraw(555.55f);
        }, "大翔-ATM");
        Thread s2 = new Thread(() -> {
            for (int i = 0; i < 8; i++) safeAcc.withdraw(555.55f);
        }, "小崔-网银");
        s1.start(); s2.start();
        s1.join(); s2.join();
        System.out.printf(">>> 安全版本最终余额: %.2f (期望: %.2f)\n",
                safeAcc.getBalance(), 8888.88f - 16 * 555.55f);
    }
}

控制台输出(不安全版本可能出现余额错误):

========== 飞翔科技银行账户安全测试 ==========

--- 不安全版本(无 synchronized)---
[大翔-ATM] 取款 555.55 成功, 余额: 8333.33
[小崔-网银] 取款 555.55 成功, 余额: 7777.78
[大翔-ATM] 取款 555.55 成功, 余额: 7222.23
[小崔-网银] 取款 555.55 成功, 余额: 6666.68
...(并发操作导致丢失更新)
[大翔-ATM] 取款 555.55 成功, 余额: 1111.10
[小崔-网银] 取款 555.55 成功, 余额: 1111.10   ← 两个线程都从 1111.10 扣了款!
>>> 不安全版本最终余额: 1111.10 (期望: 0.00)   ← 丢失更新!

--- 安全版本(synchronized 方法)---
[大翔-ATM] 取款 555.55 成功, 余额: 8333.33
[大翔-ATM] 取款 555.55 成功, 余额: 7777.78
...(串行化执行,每次扣款正确)
[小崔-网银] 取款 555.55 成功, 余额: 555.55
[小崔-网银] 取款 555.55 成功, 余额: 0.00
>>> 安全版本最终余额: 0.00 (期望: 0.00)         ← 正确!

示例 2:飞翔科技多窗口售票系统 —— synchronized 代码块精确控制锁范围

场景:飞翔科技团建,白歌负责统一购买 50 张火车票(单价 188.88 元),开启 3 个抢票窗口。使用 synchronized 代码块精确控制锁粒度。

import java.util.ArrayList;
import java.util.List;

/**
 * 飞翔科技 - 火车票售票系统 synchronized 代码块应用
 * 
 * 场景:50 张票,单价 188.88 元,3 个窗口并发售票
 *       使用 synchronized 代码块精确定义临界区,锁外执行耗时操作
 */
public class TicketSystemDemo {

    static class TicketCounter {
        private int remaining;
        private final float ticketPrice = 188.88f;

        public TicketCounter(int total) {
            this.remaining = total;
        }

        // 专用锁对象 — 比锁 this 更安全(避免外部代码误用 this 锁)
        private final Object ticketLock = new Object();

        /**
         * 售票方法 — 使用 synchronized 代码块
         * 只有操作共享变量 remaining 的代码在锁内
         * 日志打印、票据生成等耗时操作在锁外执行
         */
        public int sellTicket(String windowName, int requestCount) {
            // === 非临界区:日志、校验等不需要同步 ===
            System.out.printf("[%s] 申请购买 %d 张票 (单价 %.2f)...\n",
                    windowName, requestCount, ticketPrice);

            int actualSold;
            float totalPrice;

            // === 临界区:只锁住操作共享变量的代码 ===
            synchronized (ticketLock) {
                if (remaining >= requestCount) {
                    remaining -= requestCount;
                    actualSold = requestCount;
                    totalPrice = actualSold * ticketPrice;
                    System.out.printf("[%s] ✓ 成功购得 %d 张,小计 %.2f 元,剩余 %d 张\n",
                            windowName, actualSold, totalPrice, remaining);
                } else if (remaining > 0) {
                    actualSold = remaining;
                    totalPrice = actualSold * ticketPrice;
                    remaining = 0;
                    System.out.printf("[%s] ⚠ 票不足!仅购得 %d 张,小计 %.2f 元,已售罄\n",
                            windowName, actualSold, totalPrice);
                } else {
                    actualSold = 0;
                    totalPrice = 0;
                    System.out.printf("[%s] ✗ 票已售罄!\n", windowName);
                }
            }

            // === 非临界区:打印票据(耗时操作放锁外) ===
            if (actualSold > 0) {
                try { Thread.sleep(30); } catch (InterruptedException e) { }
                System.out.printf("[%s] 票据打印完成: %d 张,合计 %.2f 元\n",
                        windowName, actualSold, totalPrice);
            }

            return actualSold;
        }

        public int getRemaining() {
            synchronized (ticketLock) {
                return remaining;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TicketCounter counter = new TicketCounter(50);
        List<String> salesLog = new ArrayList<>();

        // 窗口1:大翔 — 每次买 3 张
        Thread w1 = new Thread(() -> {
            int total = 0;
            for (int i = 0; i < 8; i++) {
                total += counter.sellTicket("窗口1-大翔(工号10001)", 3);
                try { Thread.sleep(15); } catch (InterruptedException e) { }
            }
            salesLog.add("窗口1-大翔 共购: " + total + " 张");
        });

        // 窗口2:白歌 — 每次买 5 张
        Thread w2 = new Thread(() -> {
            int total = 0;
            for (int i = 0; i < 6; i++) {
                total += counter.sellTicket("窗口2-白歌(工号10002)", 5);
                try { Thread.sleep(15); } catch (InterruptedException e) { }
            }
            salesLog.add("窗口2-白歌 共购: " + total + " 张");
        });

        // 窗口3:小崔 — 每次买 2 张
        Thread w3 = new Thread(() -> {
            int total = 0;
            for (int i = 0; i < 10; i++) {
                total += counter.sellTicket("窗口3-小崔(工号10003)", 2);
                try { Thread.sleep(15); } catch (InterruptedException e) { }
            }
            salesLog.add("窗口3-小崔 共购: " + total + " 张");
        });

        w1.start(); w2.start(); w3.start();
        w1.join(); w2.join(); w3.join();

        System.out.println("\n========== 售票汇总 ==========");
        for (String log : salesLog) System.out.println(log);
        System.out.printf("剩余票数: %d\n", counter.getRemaining());
    }
}

控制台输出:

[窗口1-大翔(工号10001)] 申请购买 3 张票 (单价 188.88)...
[窗口1-大翔(工号10001)] ✓ 成功购得 3 张,小计 566.64 元,剩余 47 张
[窗口2-白歌(工号10002)] 申请购买 5 张票 (单价 188.88)...
[窗口2-白歌(工号10002)] ✓ 成功购得 5 张,小计 944.40 元,剩余 42 张
[窗口3-小崔(工号10003)] 申请购买 2 张票 (单价 188.88)...
[窗口3-小崔(工号10003)] ✓ 成功购得 2 张,小计 377.76 元,剩余 40 张
...(并发售票过程)
[窗口1-大翔(工号10001)] ⚠ 票不足!仅购得 1 张,小计 188.88 元,已售罄
[窗口2-白歌(工号10002)] ✗ 票已售罄!
[窗口3-小崔(工号10003)] ✗ 票已售罄!

========== 售票汇总 ==========
窗口1-大翔 共购: 21 张
窗口2-白歌 共购: 25 张
窗口3-小崔 共购: 4 张
剩余票数: 0

五、可重入性详解

synchronized 是可重入锁:同一线程可以多次获取同一把锁,Monitor 内部维护一个计数器。

/**
 * 飞翔科技 - synchronized 可重入性演示
 */
public class ReentrantDemo {

    // 所有 synchronized 实例方法共享同一把 this 锁
    public synchronized void methodA() {
        System.out.println(Thread.currentThread().getName() + " 进入 methodA");
        // methodA 内部调用 methodB — 同一线程,同一把锁,不会死锁
        methodB();  // ✓ 可重入!
    }

    public synchronized void methodB() {
        System.out.println(Thread.currentThread().getName() + " 进入 methodB");
        methodC();
    }

    public synchronized void methodC() {
        System.out.println(Thread.currentThread().getName() + " 进入 methodC");
    }

    public static void main(String[] args) {
        ReentrantDemo demo = new ReentrantDemo();
        // 线程 10001 号大翔多次进入锁
        new Thread(() -> demo.methodA(), "大翔-10001").start();
    }
}

控制台输出:

大翔-10001 进入 methodA
大翔-10001 进入 methodB
大翔-10001 进入 methodC

六、类锁 vs 实例锁

对比维度实例锁(this)类锁(Class)
修饰位置synchronized 实例方法 / synchronized(this){}synchronized 静态方法 / synchronized(ClassName.class){}
锁对象当前实例对象类的 Class 对象
作用范围同一实例上的所有同步实例方法互斥所有实例共享,全局互斥
不同实例不同实例的同步方法可并行执行无论多少个实例,静态同步方法都互斥
是否互斥实例锁和类锁互不影响实例锁和类锁互不影响
/**
 * 飞翔科技 - 类锁与实例锁互不干扰演示
 */
public class ClassLockVsInstanceLock {

    // 实例锁 — 锁定 this
    public synchronized void instanceMethod() {
        System.out.println(Thread.currentThread().getName() + " 获得实例锁");
        try { Thread.sleep(2000); } catch (InterruptedException e) { }
        System.out.println(Thread.currentThread().getName() + " 释放实例锁");
    }

    // 类锁 — 锁定 ClassLockVsInstanceLock.class
    public static synchronized void classMethod() {
        System.out.println(Thread.currentThread().getName() + " 获得类锁");
        try { Thread.sleep(2000); } catch (InterruptedException e) { }
        System.out.println(Thread.currentThread().getName() + " 释放类锁");
    }

    public static void main(String[] args) {
        ClassLockVsInstanceLock obj = new ClassLockVsInstanceLock();

        // t1 获取实例锁,t2 获取类锁 — 互不干扰,可同时执行
        Thread t1 = new Thread(() -> obj.instanceMethod(), "大翔-10001");
        Thread t2 = new Thread(() -> ClassLockVsInstanceLock.classMethod(), "白歌-10002");

        long start = System.currentTimeMillis();
        t1.start(); t2.start();
        try { t1.join(); t2.join(); } catch (InterruptedException e) { }
        long elapsed = System.currentTimeMillis() - start;
        System.out.printf("总耗时: %d ms (如果互斥则需 4000ms,并行则约 2000ms)\n", elapsed);
    }
}

七、JDK 8 偏向锁深入

JDK 8 中偏向锁默认开启,但有 4 秒启动延迟。延迟期间创建的对象直接进入轻量级锁。

/**
 * 飞翔科技 - JDK 8 偏向锁延迟验证
 * 运行时添加 JVM 参数查看锁状态:
 *   -XX:+PrintGCDetails -XX:+PrintFlagsFinal
 *   -XX:BiasedLockingStartupDelay=0  关闭延迟
 */
public class BiasedLockDemo {

    public static void main(String[] args) throws InterruptedException {
        // 延迟 6 秒后创建对象 — 此时偏向锁已激活
        System.out.println("等待偏向锁激活...");
        Thread.sleep(6000);

        Object lock = new Object();
        System.out.println("创建锁对象: " + lock);

        // 查看对象头 — 可配合 jol-core 库查看 Mark Word
        // System.out.println(ClassLayout.parseInstance(lock).toPrintable());

        long start = System.currentTimeMillis();
        // 线程 10001 号大翔反复进入同步块
        for (int i = 0; i < 100000000; i++) {
            synchronized (lock) {
                // 偏向锁模式下,几乎无开销
            }
        }
        long elapsed = System.currentTimeMillis() - start;
        System.out.printf("100,000,000 次 synchronized 执行耗时: %d ms\n", elapsed);
    }
}

八、易错场景

❌ 错误 1:用 String 常量做锁对象

// ✗ 错误:String 常量池中的对象是全局共享的
synchronized ("飞翔科技") {  // 所有用 "飞翔科技" 加锁的代码都在抢同一把锁!
    // ...
}

// ✓ 正确:使用专用 Object 或 final 成员变量
private final Object lock = new Object();
synchronized (lock) { ... }

❌ 错误 2:锁住可变对象

// ✗ 错误:Integer 在 -128~127 范围使用缓存,不同地方可能拿到同一对象
synchronized (new Integer(count)) { ... }

// ✗ 错误:锁对象被重新赋值,锁失效
private Object lock = new Object();
public void method() {
    synchronized (lock) {
        lock = new Object();  // 锁对象变了!其他线程可能拿到新对象
    }
}

// ✓ 正确:锁对象使用 final 修饰
private final Object lock = new Object();

❌ 错误 3:synchronized 修饰 run() 方法

// ✗ 错误:run() 加了 synchronized,但每个线程是不同 Runnable 实例,锁不住
class MyTask implements Runnable {
    public synchronized void run() { ... }  // 锁的是不同实例,无效!
}

// ✓ 正确:共享同一个锁对象
public void run() {
    synchronized (sharedLock) { ... }
}

❌ 错误 4:异常导致锁未释放

// synchronized 在异常时自动释放锁 — 这是正确的行为
// 但如果异常后的状态不一致(如转账扣了A的钱但抛异常没加给B),需要处理
public synchronized void transfer(Account from, Account to, float amount) {
    from.withdraw(amount);           // 扣款成功
    // 如果这里抛异常...
    to.deposit(amount);              // 未执行!钱丢了!
}
// ✓ 正确:记录操作日志或回滚,确保数据一致性

✅ 正确示范:精确控制锁粒度

// ✓ 正确:只在操作共享数据时加锁,耗时的 IO/网络操作放锁外
public void processOrder(int orderId) {
    // 锁外:查询数据库、网络请求等耗时操作
    Order order = queryOrderFromDB(orderId);

    synchronized (inventoryLock) {
        // 锁内:只操作共享的商品库存
        inventory.decrease(order.getProductId(), order.getQuantity());
    }

    // 锁外:发送通知、打印小票等
    sendNotification(order.getUserId());
}

九、面试考点

Q1:synchronized 底层是如何实现的?

A:synchronized 基于 JVM 的 Monitor 机制实现。编译后,synchronized 代码块对应 monitorenter 和 monitorexit 两条字节码指令。monitorenter 尝试获取对象的 Monitor,成功则计数器 +1;monitorexit 将计数器 -1,归零时释放锁。synchronized 方法则通过方法表(method_info)中的 ACC_SYNCHRONIZED 标志位标识,JVM 调用时自动进行 monitorenter/monitorexit。

JDK 1.6 之后引入了偏向锁、轻量级锁、重量级锁的升级机制,极大优化了性能。偏向锁在 Mark Word 中记录线程 ID,无需 CAS;轻量级锁通过 CAS 自旋;重量级锁依赖 OS Mutex。

Q2:偏向锁、轻量级锁、重量级锁的区别和升级过程?

A:

锁状态适用场景实现机制性能开销
偏向锁只有一个线程访问同步块Mark Word 记录线程 ID,无需 CAS极低
轻量级锁多线程交替访问,无实际竞争CAS 自旋 + Lock Record较低
重量级锁多线程激烈竞争OS Mutex,线程阻塞/唤醒高(用户态↔内核态切换)

升级过程:偏向锁 →(有竞争)→ 轻量级锁 →(自旋超时或竞争加剧)→ 重量级锁。单向不可逆。

Q3:synchronized 和 ReentrantLock 的区别?

A:

对比维度synchronizedReentrantLock
实现层面JVM 内置,Monitor 机制JDK API,基于 AQS
锁释放自动释放(代码块结束或异常)必须手动 unlock()(finally 中)
可中断不支持lockInterruptibly() 支持
超时获取不支持tryLock(timeout, unit) 支持
公平锁非公平可选公平/非公平
条件变量wait()/notify()多个 Condition
性能JDK 6+ 优化后接近略优(尤其高并发)

Q4:什么是可重入锁?synchronized 如何实现可重入?

A:可重入锁指同一线程可以多次获取同一把锁而不会死锁。synchronized 的可重入通过 Monitor 内部的重入计数器实现:每次 monitorenter 计数器 +1,monitorexit 计数器 -1,归零时真正释放锁。这使得一个 synchronized 方法可以调用另一个 synchronized 方法,而不会死锁。

大翔(合上笔记本):"synchronized 表面简单,底下全是学问。Mark Word、锁升级、可重入、Monitor——学完这个才算真正入门 Java 并发。"

白歌:"没错。但记住:synchronized 不是万能的。下一章我们看 volatile——轻量级的可见性保证。"

Frank:"And then Lock — when you need tryLock, interrupt, and fair lock, you must look beyond synchronized."

上一页
线程基础详解
下一页
Lock 与显式锁详解