深入Java并发:锁机制原理剖析与性能优化实战

深入Java并发:锁机制原理剖析与性能优化实战

Scroll Down

深入Java并发:锁机制原理剖析与性能优化实战

📚 文章概述

想象一下,你正在管理一个繁忙的银行🏦:多个客户同时要存取款,但现金柜只有一个。如果没有合适的"锁"
机制,就会出现混乱——两个客户同时取钱,结果账户余额对不上!

在Java并发编程中,锁机制就是这样的"银行管理规则"
🛡️。本文将用生动的比喻和真实场景,深入解析synchronized、Lock、volatile等关键锁机制,让你不仅理解原理,更能直观感受到各种锁的"
性格特点"和使用场景。

🎭 锁的"性格档案"

在深入技术细节之前,让我们先认识一下这些锁的"性格":

锁类型 性格特点 生活比喻 口头禅
synchronized 稳重可靠,JVM优化 银行保安👮‍♂️ “按规矩办事,简单有效”
ReentrantLock 功能丰富,灵活多变 智能门锁🔐 “我可以超时,可以中断,还可以公平竞争”
ReadWriteLock 读多写少,效率至上 图书馆管理员📚 “读书可以很多人一起,但写书要排队”
volatile 轻量级,保证可见性 广播📢 “我保证消息传达到每个人”
原子类 无锁编程,性能狂魔 自动售货机🤖 “CAS操作,一步到位,绝不冲突”

1. Java锁机制全景图

🏪 从"商店管理"看锁的分类

想象你开了一家商店,需要管理不同场景下的"访问控制":

  • 银行🏦:必须严格排队,一次只能一个人办理业务(悲观锁)
  • 图书馆📚:可以多人同时看书,但写书要排队(读写锁)
  • 自动售货机🤖:投币就能买,不需要排队(乐观锁)
  • VIP通道👑:会员可以插队(公平锁 vs 非公平锁)

现在让我们用这个思路来理解Java锁的分类体系!

1.1 锁的分类体系

Java锁机制按锁的性质分类按锁的状态分类按锁的实现分类按锁的特性分类悲观锁乐观锁synchronizedReentrantLockCAS操作原子类无锁状态偏向锁轻量级锁重量级锁JVM内置锁API层面锁synchronizedReentrantLockReadWriteLock可重入锁不可重入锁公平锁非公平锁独占锁共享锁互斥锁读写锁

1.2 锁的分类详解

1.2.1 按锁的性质分类

锁类型 性格特点 生活比喻 代表实现 适用场景
悲观锁 总是担心被抢,先占位再说 银行保安👮‍♂️:“先锁门,再办事” synchronized、ReentrantLock 写操作频繁,竞争激烈
乐观锁 相信大家都很文明,出问题再处理 自动售货机🤖:“投币就行,冲突了重试” AtomicInteger、AtomicReference 读操作频繁,竞争较少

🤔 思考题

为什么银行用悲观锁,而自动售货机用乐观锁?

  • 银行:钱很重要,不能出错,宁可慢一点也要安全
  • 自动售货机:商品便宜,冲突概率低,追求效率
// 悲观锁示例
public class PessimisticLock {
    private int count = 0;

    // synchronized悲观锁
    public synchronized void increment() {
        count++; // 认为会被其他线程修改,先加锁
    }
}

// 乐观锁示例
public class OptimisticLock {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        // 乐观地认为不会被修改,通过CAS操作
        count.incrementAndGet();
    }
}

1.2.2 按锁的状态分类(synchronized锁升级)

synchronized锁会根据竞争情况自动升级,从无锁→偏向锁→轻量级锁→重量级锁,详细过程见第3章。

1.2.3 按锁的实现分类

实现方式 特点 代表 优势
JVM内置锁 JVM层面实现,自动管理 synchronized 简单易用,自动优化
API层面锁 用户代码控制 ReentrantLock 功能丰富,灵活控制
// JVM内置锁
public class JVMBuiltinLock {
    public synchronized void method1() {
        // JVM自动管理锁的获取和释放
    }
}

// API层面锁
public class APILock {
    private final ReentrantLock lock = new ReentrantLock();

    public void method2() {
        lock.lock();
        try {
            // 用户代码控制锁的获取和释放
        } finally {
            lock.unlock();
        }
    }
}

1.2.4 按锁的特性分类

特性 说明 代表实现 使用场景
可重入锁 同一线程可多次获取同一把锁 synchronized、ReentrantLock 递归调用、方法调用链
不可重入锁 同一线程不能重复获取 自定义锁 特殊业务场景
公平锁 按请求顺序获取锁 ReentrantLock(true) 需要公平性的场景
非公平锁 不按顺序,允许插队 synchronized、ReentrantLock(false) 追求高吞吐量
独占锁 同时只能有一个线程持有 synchronized、ReentrantLock 写操作
共享锁 多个线程可以同时持有 ReadWriteLock.readLock() 读操作
互斥锁 同一时间只能有一个线程访问 synchronized 临界区保护
读写锁 读读共享,读写互斥,写写互斥 ReadWriteLock 读多写少场景
public class LockCharacteristicsDemo {
    // 可重入锁示例
    public synchronized void method1() {
        System.out.println("method1获取锁");
        method2(); // 可重入调用
    }

    public synchronized void method2() {
        System.out.println("method2获取锁"); // 同一线程可重复获取
    }

    // 公平锁 vs 非公平锁
    private final ReentrantLock fairLock = new ReentrantLock(true);    // 公平锁
    private final ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁

    // 读写锁示例
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void read() {
        rwLock.readLock().lock(); // 共享锁
        try {
            // 多个线程可以同时读取
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void write() {
        rwLock.writeLock().lock(); // 独占锁
        try {
            // 只有一个线程可以写入
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

1.3 锁的选择决策树:如何选择合适的"门卫"?

🎯 选择锁的"黄金法则"

需要同步控制是否需要超时控制?ReentrantLock智能门卫🔐读多写少?ReadWriteLock图书馆管理员📚高并发计数器?AtomicInteger/LongAdder自动售货机🤖简单同步?synchronized银行保安👮‍♂️ReentrantLock智能门卫🔐支持tryLock、可中断我可以等,也可以不等读读共享,读写互斥读书可以一起,写书要排队无锁,高性能CAS操作,一步到位JVM优化,简单易用按规矩办事,简单有效功能丰富,灵活控制我可以做很多事

🎭 锁的"性格测试"

场景1:银行取款 💰

  • 选择:synchronized(银行保安)
  • 原因:简单可靠,JVM优化,适合一般同步需求

场景2:图书馆借书 📚

  • 选择:ReadWriteLock(图书馆管理员)
  • 原因:多人可以同时看书,但借书要排队

场景3:网站访问计数器 🔢

  • 选择:AtomicInteger(自动售货机)
  • 原因:高并发,无锁,性能最优

场景4:需要超时控制的资源

  • 选择:ReentrantLock(智能门卫)
  • 原因:支持tryLock,可以设置超时时间

1.4 锁的"能力值"对比

锁类型 性能 功能 易用性 适用场景 特点
synchronized ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 一般同步需求 JVM优化,简单可靠
ReentrantLock ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 复杂同步需求 功能丰富,支持超时
ReadWriteLock ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 读多写少 读写分离,高并发读取
AtomicInteger ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐ 计数器场景 无锁,原子操作
LongAdder ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐ 高并发累加 高并发优化,最终一致
volatile ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 状态标志 轻量级,保证可见性

🏆 各锁的"专长领域"

  • synchronized:JVM自动优化,简单可靠,适合一般同步需求
  • ReentrantLock:功能丰富,支持超时和中断,适合复杂场景
  • ReadWriteLock:读写分离,适合读多写少场景
  • 原子类:无锁编程,适合高并发计数
  • volatile:轻量级同步,适合状态标志

1.5 学习路径建议:从"新手村"到"大师级"

🎮 锁机制学习"游戏攻略"

第一阶段:新手村(基础阶段)

  1. synchronized:银行保安👮‍♂️ - 简单可靠,适合入门
  2. volatile:广播📢 - 轻量级,理解可见性
  3. 锁升级:智能门卫🚀 - 理解JVM优化

第二阶段:进阶副本(进阶阶段)

  1. ReentrantLock:智能门卫🔐 - 功能丰富,灵活控制
  2. ReadWriteLock:图书馆管理员📚 - 读多写少场景
  3. 原子类:自动售货机🤖 - 无锁编程,高性能

第三阶段:大师级(实战阶段)

  1. 综合应用:多锁组合使用
  2. 性能优化:根据场景选择最优锁
  3. 最佳实践:避免常见陷阱

🎯 学习建议

  • 循序渐进:不要急于求成,先理解基础概念
  • 动手实践:每个锁都要写代码验证
  • 场景驱动:结合实际业务场景理解锁的选择
  • 性能测试:用数据说话,验证不同锁的性能

现在我们对Java锁机制有了全局认识,接下来我们将深入每个具体的锁机制实现。准备好了吗?让我们开始这场"锁的冒险"!🚀


2. synchronized关键字深度解析

🏦 银行保安的"三种工作方式"

synchronized就像银行保安👮‍♂️,有三种不同的"工作方式"来保护不同的区域:

2.1 synchronized的三种使用方式

2.1.1 同步实例方法:保护"个人办公室" 🏢

public class SynchronizedExample {
    private int count = 0;

    // 同步实例方法,锁对象是this(当前对象)
    // 就像保安保护这个员工的个人办公室
    public synchronized void increment() {
        count++;
    }

    // 等价于以下写法
    public void increment2() {
        synchronized (this) {  // 明确指定锁对象
            count++;
        }
    }
}

生活比喻:就像银行保安保护某个员工的个人办公室,只有这个员工可以进入。

2.1.2 同步静态方法:保护"银行总部" 🏛️

public class SynchronizedExample {
    private static int staticCount = 0;

    // 同步静态方法,锁对象是Class对象
    // 就像保安保护整个银行总部
    public static synchronized void incrementStatic() {
        staticCount++;
    }

    // 等价于以下写法
    public static void incrementStatic2() {
        synchronized (SynchronizedExample.class) {  // 锁住整个类
            staticCount++;
        }
    }
}

生活比喻:就像银行保安保护整个银行总部,所有员工都要遵守总部的规则。

2.1.3 同步代码块:保护"特定区域" 🔒

public class SynchronizedExample {
    private final Object lock = new Object();  // 专用锁对象
    private int count = 0;

    public void increment() {
        // 同步代码块,可以指定任意对象作为锁
        // 就像保安保护特定的保险柜
        synchronized (lock) {
            count++;
        }
    }
}

生活比喻:就像银行保安保护特定的保险柜,只有持有钥匙的人才能打开。

🤔 思考题

为什么同步代码块比同步方法更灵活?

  • 同步方法:锁住整个方法,粒度大
  • 同步代码块:只锁住必要的代码,粒度小,性能更好

2.2 synchronized底层原理:保安的"工作手册"

🔍 深入理解:synchronized是如何工作的?

synchronized的底层实现基于JVM的monitor机制,就像银行保安有一套完整的"工作手册":

2.2.1 Monitor对象结构:保安的"工作记录"

每个Java对象都有一个monitor对象,就像每个银行都有保安的工作记录:

// 简化的Monitor对象结构
class Monitor {
    Object owner;           // 当前持有锁的线程(谁在值班)
    int count;             // 重入次数(同一线程进入次数)
    WaitSet waitSet;       // 等待队列(排队等待的线程)
    EntryList entryList;   // 阻塞队列(被阻塞的线程)
}

生活比喻

  • owner:当前值班的保安
  • count:保安进入房间的次数(可重入)
  • waitSet:等待进入的访客队列
  • entryList:被拦在外面的访客队列

2.2.2 字节码层面分析:保安的"工作流程"

🔍 同步方法的字节码:自动管理

public class SynchronizedBytecode {
    private int count = 0;

    public synchronized void method() {
        count++;
    }
}

对应的字节码指令

// 方法访问标志包含ACC_SYNCHRONIZED
public synchronized void method();

descriptor:()V
flags:ACC_PUBLIC,ACC_SYNCHRONIZED  // 注意这个标志
Code:
stack=3,locals=1,args_size=1
        0:aload_0
         1:dup
         2:getfield      #2  // Field count:I
        5:iconst_1
         6:iadd
         7:putfield      #2  // Field count:I
        10:return

生活比喻:就像保安有"自动门禁系统",进入时自动刷卡,离开时自动放行。

🔍 同步代码块的字节码:手动管理

public void synchronizedBlock() {
    synchronized (this) {
        count++;
    }
}

对应字节码

public void synchronizedBlock();

descriptor:()V
flags:ACC_PUBLIC
Code:
stack=3,locals=3,args_size=1
        0:aload_0
         1:dup
         2:astore_1
         3:monitorenter          // 获取锁(保安开始值班)
         4:aload_0
         5:dup
         6:getfield      #2      // Field count:I
        9:iconst_1
        10:iadd
        11:putfield      #2      // Field count:I
        14:aload_1
        15:monitorexit           // 释放锁(保安下班)
        16:goto 24
                19:astore_2
        20:aload_1
        21:monitorexit           // 异常时也要释放锁(紧急情况也要下班)
        22:aload_2
        23:athrow
        24:return

生活比喻:就像保安手动开关门,进入时手动开门,离开时手动关门,即使发生意外也要确保关门。

🤔 思考题

为什么同步代码块有两个monitorexit?

  • 正常情况:第15行的monitorexit
  • 异常情况:第21行的monitorexit
  • 确保无论什么情况,锁都会被释放

3. 锁升级过程详解

🚀 智能保安的"进化史"

Java 6之后,synchronized引入了锁升级机制,就像银行保安会根据"访客情况"自动调整管理策略,从简单到复杂,以提高性能。

3.1 锁的状态:保安的"工作模式"

Java对象头中的Mark Word记录了锁的状态,就像保安的工作记录:

// 32位JVM中Mark Word的结构
// 锁状态标志位:01-无锁/偏向锁,00-轻量级锁,10-重量级锁,11-GC标记

生活比喻

  • 01:无人值守模式(无锁/偏向锁)
  • 00:简单管理模式(轻量级锁)
  • 10:严格管理模式(重量级锁)
  • 11:特殊状态(GC标记)

3.2 偏向锁(Biased Locking):VIP客户通道 👑

🎯 偏向锁的"性格特点"

偏向锁是JDK 6引入的优化,就像银行给VIP客户开设的专用通道,适用于只有一个线程访问同步块的场景。

3.2.1 偏向锁的获取过程:VIP客户识别

public class BiasedLockExample {
    private int count = 0;

    public synchronized void method() {
        count++; // 偏向锁:第一次访问记录"常客"身份,以后直接放行
    }
}

偏向锁获取过程(VIP客户识别流程):

  1. 检查身份:查看Mark Word中的线程ID是否指向当前线程
  2. 直接放行:如果是VIP客户,直接执行同步代码
  3. 身份验证:如果不是,检查偏向锁标志位
  4. 申请VIP:如果为0,使用CAS操作竞争偏向锁
  5. 撤销VIP:如果为1,说明存在竞争,撤销偏向锁

生活比喻

  • 银行给经常来的客户办VIP卡
  • 以后来银行,直接走VIP通道
  • 如果发现不是VIP客户,就撤销VIP资格

3.2.2 偏向锁的撤销:VIP通道关闭

public class BiasedLockRevocation {
    private int count = 0;

    public synchronized void method1() {
        count++; // 第一个线程:申请VIP成功
    }

    public synchronized void method2() {
        count--; // 第二个线程:发现竞争,申请撤销VIP
    }
}

偏向锁撤销过程(VIP通道关闭):

  1. 发现竞争:多个线程竞争同一个锁
  2. 撤销VIP:偏向锁被撤销
  3. 升级管理:升级为轻量级锁

生活比喻

  • 银行发现VIP通道被多人使用
  • 关闭VIP通道,改为普通通道
  • 所有客户都要排队等待

3.3 轻量级锁(Lightweight Locking):简单排队管理 👨‍💼

🎯 轻量级锁的"性格特点"

轻量级锁适用于线程交替执行同步块的场景,就像小区门卫的简单管理方式。

3.3.1 轻量级锁的获取过程:简单登记

public class LightweightLockExample {
    private int count = 0;

    public synchronized void method() {
        count++; // 轻量级锁:简单登记,没有激烈竞争
    }
}

轻量级锁获取过程(简单登记流程):

  1. 准备登记表:在栈帧中创建Lock Record
  2. 备份信息:将Mark Word复制到Lock Record的Displaced Mark Word
  3. 尝试登记:使用CAS操作将Mark Word指向Lock Record
  4. 登记成功:如果成功,获取轻量级锁
  5. 升级管理:如果失败,说明存在竞争,升级为重量级锁

生活比喻

  • 小区门卫准备访客登记表
  • 备份原来的门禁信息
  • 尝试登记新访客
  • 如果登记成功,访客可以进入
  • 如果登记失败,说明有冲突,需要更严格的管理

3.4 重量级锁(Heavyweight Locking):严格排队管理 👮‍♂️

🎯 重量级锁的"性格特点"

重量级锁适用于多线程竞争激烈的场景,就像银行大厅的严格管理方式。

3.4.1 重量级锁的实现:严格管理

public class HeavyweightLockExample {
    private int count = 0;

    public synchronized void method() {
        count++; // 重量级锁:严格排队,确保安全
    }
}

重量级锁特点(严格管理特点):

  • 系统级管理:使用操作系统的mutex实现
  • 严格排队:线程阻塞和唤醒需要系统调用
  • 性能代价:性能开销较大,但能保证公平性

生活比喻

  • 银行大厅的严格排队管理
  • 使用银行总部的管理系统
  • 客户要排队等待叫号
  • 虽然慢一些,但绝对公平安全

3.5 锁升级示例:保安管理策略的"进化"

public class LockUpgradeDemo {
    private int count = 0;

    public synchronized void method() {
        count++; // 锁会根据访问情况自动升级
    }

    public static void main(String[] args) throws InterruptedException {
        LockUpgradeDemo demo = new LockUpgradeDemo();

        // 阶段1:单线程访问,使用偏向锁
        demo.method();

        // 阶段2:多线程竞争,升级为轻量级锁
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.method();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.method();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final count: " + demo.count);
    }
}

🎭 锁升级的"故事"

银行保安根据访客情况调整管理策略:无锁状态(无人值守)→ 偏向锁(VIP通道)→ 轻量级锁(简单登记)→ 重量级锁(严格排队)。


4. volatile关键字与内存屏障

📢 广播系统:volatile的"工作方式"

volatile是Java提供的一种轻量级的同步机制,就像广播系统📢,主要用于保证变量的可见性和有序性。

4.1 volatile的特性:广播的"三大特点"

volatile关键字具有以下特性:

特性 生活比喻 技术说明 注意事项
可见性 广播📢:消息传达到每个人 一个线程对volatile变量的修改对其他线程立即可见 保证数据一致性
有序性 按顺序广播📻:不能乱序播放 禁止指令重排序 保证执行顺序
不保证原子性 广播不能保证操作完成 对于复合操作不保证原子性 需要配合其他机制

🤔 思考题

为什么volatile不保证原子性?

  • 广播只能保证消息传达,不能保证接收者完成操作
  • 就像广播"开始工作",但不能保证每个人都完成工作

4.2 volatile的实现原理:广播系统的"技术细节"

4.2.1 内存屏障(Memory Barrier):广播的"信号塔"

volatile通过内存屏障实现可见性和有序性,就像广播系统通过信号塔确保消息传达:

public class VolatileExample {
    private volatile boolean flag = false;  // 广播信号
    private int count = 0;                  // 普通数据

    public void writer() {
        count = 1;          // 普通写操作(本地操作)
        flag = true;        // volatile写操作,会插入StoreStore屏障(广播信号)
    }

    public void reader() {
        if (flag) {         // volatile读操作,会插入LoadLoad屏障(接收广播)
            int temp = count; // 普通读操作(本地操作)
        }
    }
}

生活比喻:volatile写操作通过广播塔发送信号,volatile读操作接收广播信号,确保消息传达。

4.2.2 内存屏障类型:广播系统的"信号类型"

public class MemoryBarrierExample {
    private volatile int x = 0;  // 广播信号1
    private volatile int y = 0;  // 广播信号2
    private int a = 0;           // 普通数据1
    private int b = 0;           // 普通数据2

    // 线程1:发送广播
    public void thread1() {
        a = 1;              // 普通写(本地记录)
        x = 1;              // volatile写,插入StoreStore屏障(发送广播信号)
    }

    // 线程2:接收广播
    public void thread2() {
        y = 1;              // volatile写,插入StoreStore屏障(发送广播信号)
        b = 1;              // 普通写(本地记录)
    }
}

🎯 内存屏障的"四种类型"

屏障类型 生活比喻 技术说明 作用
LoadLoad 接收广播前检查 读操作之间的屏障 确保读操作顺序
StoreStore 发送广播前检查 写操作之间的屏障 确保写操作顺序
LoadStore 接收后发送 读操作和写操作之间的屏障 确保读写顺序
StoreLoad 发送后接收 写操作和读操作之间的屏障 确保写读顺序

生活比喻:内存屏障就像广播系统的信号检查机制,确保广播发送和接收的顺序正确。

4.3 volatile的使用场景:广播系统的"应用场景"

4.3.1 状态标志:紧急广播系统 🚨

public class StatusFlag {
    private volatile boolean running = true;  // 紧急广播信号

    public void start() {
        new Thread(() -> {
            while (running) {  // 监听广播信号
                // 执行任务
                doWork();
            }
        }).start();
    }

    public void stop() {
        running = false; // 发送紧急广播,其他线程能立即看到这个变化
    }

    private void doWork() {
        // 模拟工作
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

生活比喻:就像紧急广播系统,主控室发送"停止工作"信号,所有工作人员立即停止工作。

4.3.2 双重检查锁定(DCL):智能广播系统 📡

public class Singleton {
    private volatile static Singleton instance;  // 广播信号:实例状态

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {                    // 第一次检查(快速检查)
            synchronized (Singleton.class) {       // 获取锁(进入临界区)
                if (instance == null) {            // 第二次检查(安全检查)
                    instance = new Singleton();    // volatile保证可见性(广播创建完成)
                }
            }
        }
        return instance;
    }
}

生活比喻:就像智能广播系统,第一次快速检查,进入广播室后再次确认,然后发送广播通知所有人。

🤔 思考题

为什么需要两次检查?

  • 第一次检查:快速检查,避免不必要的锁竞争
  • 第二次检查:安全检查,确保只有一个实例被创建

4.4 volatile的局限性:广播系统的"限制"

⚠️ 广播系统的"不足之处"

public class VolatileLimitation {
    private volatile int count = 0;  // 广播信号

    // 错误示例:volatile不保证原子性
    public void increment() {
        count++; // 这个操作不是原子的(广播不能保证操作完成)
    }

    // 正确示例:使用synchronized保证原子性
    public synchronized void incrementSafe() {
        count++; // 银行保安确保操作安全
    }

    // 或者使用原子类
    private AtomicInteger atomicCount = new AtomicInteger(0);

    public void incrementAtomic() {
        atomicCount.incrementAndGet(); // 自动售货机,一步到位
    }
}

🎯 volatile的"三大限制"

限制 生活比喻 技术说明 解决方案
不保证原子性 广播不能保证操作完成 复合操作不是原子的 使用synchronized或原子类
性能开销 广播需要消耗电力 内存屏障有性能开销 合理使用,避免过度使用
功能有限 广播只能传达消息 功能相对简单 复杂场景使用其他锁机制

🤔 思考题

什么时候使用volatile?

  • 适合使用:状态标志、双重检查锁定
  • 不适合使用:需要原子性的复合操作
  • 选择原则:简单状态用volatile,复杂操作用synchronized

5. ReentrantLock与ReadWriteLock

🔐 智能门卫:ReentrantLock的"超能力"

ReentrantLock是Java 5引入的可重入锁,就像智能门卫🔐,提供了比synchronized更灵活的锁机制。

5.1 ReentrantLock详解:智能门卫的"功能特点"

🎯 ReentrantLock vs synchronized 对比

特性 synchronized ReentrantLock 生活比喻
可重入性 ✅ 支持 ✅ 支持 同一员工可以多次进入办公室
超时控制 ❌ 不支持 ✅ 支持 智能门卫可以设置等待时间
可中断性 ❌ 不支持 ✅ 支持 智能门卫可以被中断
公平性 ❌ 非公平 ✅ 可配置 智能门卫可以公平排队
条件等待 ❌ 不支持 ✅ 支持 智能门卫可以设置等待条件

5.1.1 基本使用:智能门卫的"标准操作"

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();  // 智能门卫
    private int count = 0;

    public void increment() {
        lock.lock();        // 智能门卫开始值班
        try {
            count++;        // 执行任务
        } finally {
            lock.unlock();  // 智能门卫下班(必须在finally中释放锁)
        }
    }

    public int getCount() {
        lock.lock();        // 智能门卫开始值班
        try {
            return count;   // 查看数据
        } finally {
            lock.unlock();  // 智能门卫下班
        }
    }
}

生活比喻

  • 智能门卫开始值班:lock.lock()
  • 执行任务:在保护区域内工作
  • 智能门卫下班:lock.unlock()
  • 必须下班:finally确保门卫一定会下班

5.1.2 可重入性演示:智能门卫的"VIP特权"

public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();  // 智能门卫

    public void method1() {
        lock.lock();        // 智能门卫开始值班
        try {
            System.out.println("method1获取锁 - 进入办公室");
            method2();      // 可重入:同一员工可以进入其他房间
        } finally {
            lock.unlock();  // 智能门卫下班
        }
    }

    public void method2() {
        lock.lock();        // 智能门卫继续值班(同一员工)
        try {
            System.out.println("method2获取锁 - 进入会议室");
        } finally {
            lock.unlock();  // 智能门卫下班
        }
    }
}

生活比喻:员工有VIP卡,可以进入多个房间,智能门卫识别是同一员工,允许进入。

5.1.3 公平锁与非公平锁:智能门卫的"排队策略"

public class FairLockExample {
    // 公平锁:按顺序排队
    private final ReentrantLock fairLock = new ReentrantLock(true);

    // 非公平锁:允许插队(默认)
    private final ReentrantLock unfairLock = new ReentrantLock(false);

    public void fairMethod() {
        fairLock.lock();        // 公平门卫:按顺序排队
        try {
            System.out.println("公平锁执行 - 按顺序排队");
        } finally {
            fairLock.unlock();
        }
    }

    public void unfairMethod() {
        unfairLock.lock();      // 非公平门卫:允许插队
        try {
            System.out.println("非公平锁执行 - 允许插队");
        } finally {
            unfairLock.unlock();
        }
    }
}

🎯 公平锁 vs 非公平锁 对比

特性 公平锁 非公平锁 生活比喻
排队方式 按顺序排队 允许插队 银行排队 vs 地铁排队
性能 较低 较高 公平但慢 vs 不公平但快
适用场景 需要公平性 追求高吞吐量 重要业务 vs 高并发场景
默认选择 需要手动设置 默认模式 特殊需求 vs 一般需求

生活比喻:公平锁像银行排队(先来先服务),非公平锁像地铁排队(可以插队)。

5.1.4 尝试获取锁:智能门卫的"灵活策略"

public class TryLockExample {
    private final ReentrantLock lock = new ReentrantLock();  // 智能门卫

    public void tryLockMethod() {
        if (lock.tryLock()) {        // 尝试获取锁(不等待)
            try {
                System.out.println("成功获取锁 - 门卫允许进入");
                // 执行业务逻辑
            } finally {
                lock.unlock();       // 释放锁
            }
        } else {
            System.out.println("获取锁失败,执行其他逻辑 - 门卫拒绝,去其他地方");
        }
    }

    public void tryLockWithTimeout() {
        try {
            if (lock.tryLock(1, TimeUnit.SECONDS)) {  // 尝试获取锁(等待1秒)
                try {
                    System.out.println("在1秒内成功获取锁 - 门卫在1秒内允许进入");
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("1秒内未获取到锁 - 门卫1秒内没有允许进入");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();  // 处理中断
        }
    }
}

🎯 尝试获取锁的"三种策略"

策略 方法 生活比喻 适用场景
立即尝试 tryLock() 门卫立即决定 不等待,失败就做其他事
超时等待 tryLock(time, unit) 门卫等待一定时间 可以等待,但有限制
阻塞等待 lock() 门卫一直等待 必须获取锁,可以无限等待

生活比喻:立即尝试(门卫立即决定),超时等待(门卫等待1分钟),阻塞等待(门卫一直等待)。

5.2 ReadWriteLock详解:图书馆管理员 📚

🎯 ReadWriteLock的"管理策略"

ReadWriteLock就像图书馆管理员📚,允许多个读者同时看书,但写书要排队。

5.2.1 基本使用:图书馆的"管理规则"

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();  // 图书馆管理员
    private int value = 0;  // 图书馆的书籍

    public int read() {
        lock.readLock().lock();    // 获取读锁(允许多人同时看书)
        try {
            return value;          // 查看书籍
        } finally {
            lock.readLock().unlock();  // 释放读锁
        }
    }

    public void write(int newValue) {
        lock.writeLock().lock();   // 获取写锁(写书要排队)
        try {
            value = newValue;      // 修改书籍
        } finally {
            lock.writeLock().unlock();  // 释放写锁
        }
    }
}

🎯 ReadWriteLock的"三大规则"

规则 生活比喻 技术说明 性能特点
读读共享 多人可以同时看书 多个线程可以同时获取读锁 高并发读取
读写互斥 看书时不能写书 读锁和写锁互斥 保证数据一致性
写写互斥 写书要排队 写锁之间互斥 保证写操作安全

生活比喻:读读共享(多人同时看书),读写互斥(看书时不能写书),写写互斥(写书要排队)。

5.2.2 读写锁性能对比:图书馆的"效率测试"

public class ReadWriteLockPerformance {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();  // 图书馆管理员
    private final Map<String, String> cache = new HashMap<>();        // 图书馆的书籍

    public String get(String key) {
        lock.readLock().lock();    // 获取读锁(多人可以同时看书)
        try {
            return cache.get(key); // 查看书籍
        } finally {
            lock.readLock().unlock();
        }
    }

    public void put(String key, String value) {
        lock.writeLock().lock();   // 获取写锁(写书要排队)
        try {
            cache.put(key, value); // 修改书籍
        } finally {
            lock.writeLock().unlock();
        }
    }

    // 性能测试:图书馆的"效率测试"
    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockPerformance demo = new ReadWriteLockPerformance();

        // 写入测试:写书测试
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            demo.put("key" + i, "value" + i);
        }
        long writeTime = System.currentTimeMillis() - start;

        // 读取测试:看书测试
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            demo.get("key" + (i % 1000));
        }
        long readTime = System.currentTimeMillis() - start;

        System.out.println("写入时间: " + writeTime + "ms");
        System.out.println("读取时间: " + readTime + "ms");
        System.out.println("读多写少场景,ReadWriteLock性能优势明显!");
    }
}

🎯 ReadWriteLock vs synchronized 性能对比

场景 ReadWriteLock synchronized 性能差异 生活比喻
读多写少 高并发读取 串行读取 ReadWriteLock优势明显 图书馆 vs 单人阅览室
写多读少 写操作串行 写操作串行 性能相近 写书都要排队
读写均衡 中等性能 中等性能 性能相近 平衡场景

生活比喻:读多写少(图书馆),写多读少(出版社),读写均衡(学校)。


6. 原子操作类详解

🤖 自动售货机:原子操作类的"无锁世界"

Java提供了丰富的原子操作类,位于java.util.concurrent.atomic包中,就像自动售货机🤖,用于实现无锁的线程安全操作。

6.1 基本原子类型:自动售货机的"商品类型"

6.1.1 AtomicInteger:整数自动售货机 🔢

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    private final AtomicInteger count = new AtomicInteger(0);  // 整数自动售货机

    public void increment() {
        count.incrementAndGet(); // 原子递增(投币买商品)
    }

    public void decrement() {
        count.decrementAndGet(); // 原子递减(退币)
    }

    public int get() {
        return count.get(); // 获取当前值(查看库存)
    }

    public void set(int value) {
        count.set(value); // 设置值(补货)
    }

    public boolean compareAndSet(int expect, int update) {
        return count.compareAndSet(expect, update); // CAS操作(智能交易)
    }
}

🎯 原子操作类的"三大特点"

特点 生活比喻 技术说明 优势
无锁 自动售货机不需要人工 使用CAS操作,无需锁 高性能,无阻塞
原子性 投币买商品,一步到位 操作不可分割 保证数据一致性
线程安全 多人同时使用售货机 多线程安全 并发安全

生活比喻:自动售货机不需要人工管理,投币买商品一步完成,多人同时使用不会出错。

6.1.2 AtomicLong:长整数自动售货机 🔢

import java.util.concurrent.atomic.AtomicLong;

public class AtomicLongExample {
    private final AtomicLong counter = new AtomicLong(0);  // 长整数自动售货机

    public long increment() {
        return counter.incrementAndGet();  // 原子递增(投币买商品)
    }

    public long add(long delta) {
        return counter.addAndGet(delta);   // 原子加法(批量投币)
    }

    public long get() {
        return counter.get();              // 获取当前值(查看库存)
    }
}

6.1.3 AtomicBoolean:布尔值自动售货机 ✅❌

import java.util.concurrent.atomic.AtomicBoolean;

public class AtomicBooleanExample {
    private final AtomicBoolean flag = new AtomicBoolean(false);  // 布尔值自动售货机

    public boolean setTrue() {
        return flag.compareAndSet(false, true);  // 设置为true(投币买商品)
    }

    public boolean setFalse() {
        return flag.compareAndSet(true, false);  // 设置为false(退币)
    }

    public boolean get() {
        return flag.get();                       // 获取当前值(查看状态)
    }
}

🎯 原子操作类的"家族成员"

类型 生活比喻 适用场景 特点
AtomicInteger 整数自动售货机 计数器、索引 最常用
AtomicLong 长整数自动售货机 大数值计数 支持更大数值
AtomicBoolean 布尔值自动售货机 状态标志 开关控制
AtomicReference 引用自动售货机 对象引用 引用类型
LongAdder 高并发累加器 高并发计数 性能最优

生活比喻:AtomicInteger(普通售货机),AtomicLong(大容量售货机),AtomicBoolean(开关售货机),AtomicReference(复杂商品售货机),LongAdder(高并发售货机)。

6.2 原子引用类型:复杂商品自动售货机 🎁

6.2.1 AtomicReference:引用自动售货机

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    private final AtomicReference<String> value = new AtomicReference<>("initial");  // 引用自动售货机

    public void updateValue(String newValue) {
        String oldValue = value.get();  // 获取当前商品
        while (!value.compareAndSet(oldValue, newValue)) {  // 尝试更换商品
            oldValue = value.get();     // 重新获取最新商品(可能被其他人换了)
        }
    }

    public String getValue() {
        return value.get();  // 获取当前商品
    }
}

生活比喻:自动售货机里的商品可以被更换,投币时检查商品是否还是原来的,如果被换了就重新检查。

6.2.2 原子更新字段:远程控制自动售货机 🎮

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicFieldUpdaterExample {
    private volatile int count = 0;  // 远程控制的商品数量
    private static final AtomicIntegerFieldUpdater<AtomicFieldUpdaterExample> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicFieldUpdaterExample.class, "count");

    public void increment() {
        updater.incrementAndGet(this);  // 远程控制增加商品
    }

    public int getCount() {
        return count;  // 查看商品数量
    }
}

生活比喻:远程控制自动售货机,通过远程控制器更新商品数量,节省内存,提高性能。

6.3 原子数组类型:多商品自动售货机 🏪

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicArrayExample {
    private final AtomicIntegerArray array = new AtomicIntegerArray(10);  // 多商品自动售货机

    public void set(int index, int value) {
        array.set(index, value);  // 设置指定位置的商品数量
    }

    public int get(int index) {
        return array.get(index);  // 获取指定位置的商品数量
    }

    public int incrementAndGet(int index) {
        return array.incrementAndGet(index);  // 增加指定位置的商品数量
    }

    public boolean compareAndSet(int index, int expect, int update) {
        return array.compareAndSet(index, expect, update);  // 智能更新指定位置的商品
    }
}

生活比喻:多商品自动售货机,每个位置都有独立的商品,可以独立操作每个位置。

6.4 原子累加器:高并发自动售货机 🚀

import java.util.concurrent.atomic.LongAdder;

public class LongAdderExample {
    private final LongAdder adder = new LongAdder();  // 高并发自动售货机

    public void increment() {
        adder.increment();  // 增加一个商品(高并发优化)
    }

    public void add(long value) {
        adder.add(value);   // 增加指定数量的商品
    }

    public long sum() {
        return adder.sum(); // 查看总商品数量
    }

    public void reset() {
        adder.reset();      // 重置商品数量
    }

    public long sumThenReset() {
        return adder.sumThenReset();  // 查看并重置商品数量
    }
}

🎯 LongAdder vs AtomicLong 性能对比

特性 LongAdder AtomicLong 生活比喻
高并发性能 优秀 一般 高并发自动售货机 vs 普通自动售货机
内存占用 较高 较低 多通道 vs 单通道
适用场景 高并发累加 一般计数 网站访问统计 vs 简单计数
精度 最终一致 强一致 最终准确 vs 实时准确

生活比喻:LongAdder(高并发售货机,多通道工作),AtomicLong(普通售货机,单通道工作)。

6.5 原子操作类性能对比:自动售货机的"效率测试"

public class AtomicPerformanceTest {
    private static final int THREAD_COUNT = 10;      // 10个客户
    private static final int OPERATION_COUNT = 1000000;  // 每个客户买100万次商品

    public static void main(String[] args) throws InterruptedException {
        System.out.println("🚀 开始性能测试:10个客户,每个客户买100万次商品");

        // synchronized测试:银行保安管理
        testSynchronized();

        // AtomicInteger测试:普通自动售货机
        testAtomicInteger();

        // LongAdder测试:高并发自动售货机
        testLongAdder();
    }

    private static void testSynchronized() throws InterruptedException {
        final int[] count = {0};
        final Object lock = new Object();  // 银行保安

        long start = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    synchronized (lock) {  // 银行保安管理
                        count[0]++;
                    }
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long time = System.currentTimeMillis() - start;
        System.out.println("🏦 Synchronized (银行保安): " + time + "ms, count: " + count[0]);
    }

    private static void testAtomicInteger() throws InterruptedException {
        final AtomicInteger count = new AtomicInteger(0);  // 普通自动售货机

        long start = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    count.incrementAndGet();  // 普通自动售货机
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long time = System.currentTimeMillis() - start;
        System.out.println("🤖 AtomicInteger (普通自动售货机): " + time + "ms, count: " + count.get());
    }

    private static void testLongAdder() throws InterruptedException {
        final LongAdder adder = new LongAdder();  // 高并发自动售货机

        long start = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    adder.increment();  // 高并发自动售货机
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long time = System.currentTimeMillis() - start;
        System.out.println("🚀 LongAdder (高并发自动售货机): " + time + "ms, count: " + adder.sum());
    }
}

🎯 性能测试结果分析

6.5.1 典型测试结果数据

在10个线程,每个线程执行100万次操作的测试环境下,典型结果如下:

锁机制 执行时间 相对性能 内存占用 适用场景
synchronized 2000-3000ms 基准 一般同步需求
AtomicInteger 800-1200ms 2-3倍 简单原子操作
LongAdder 400-600ms 4-6倍 中等 高并发累加

6.5.2 性能差异深度分析

🔍 synchronized性能特点:

// synchronized的性能瓶颈
synchronized (lock){
count++; // 每次操作都需要获取和释放锁
        }
  • 优势:JVM自动优化,简单可靠,内存占用低
  • 劣势:每次操作都需要获取锁,高并发下竞争激烈
  • 适用场景:低到中等并发,简单同步需求

🚀 AtomicInteger性能特点:

// AtomicInteger的CAS操作
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
  • 优势:无锁操作,CAS机制,性能较好
  • 劣势:高并发下CAS失败率高,需要重试
  • 适用场景:中等并发,简单原子操作

⚡ LongAdder性能特点:

// LongAdder的分段累加策略
public void increment() {
    Cell[] as;
    long b, v;
    int m;
    Cell a;
    if ((as = cells) != null || !casBase(b = base, b + 1)) {
        // 使用分段累加,减少竞争
    }
}
  • 优势:分段累加,减少竞争,高并发性能最优
  • 劣势:内存占用较高,最终一致性
  • 适用场景:高并发累加,对实时性要求不高

6.5.3 不同并发度下的性能对比

线程数 synchronized AtomicInteger LongAdder 推荐选择
1-2个 1000ms 800ms 600ms synchronized
4-8个 2000ms 1000ms 500ms AtomicInteger
16+个 4000ms 2000ms 400ms LongAdder

6.5.4 性能选择决策树

1 - 4个4 - 16个16+个需要原子操作并发线程数?选择synchronized简单可靠,性能足够选择AtomicInteger无锁,性能较好选择LongAdder高并发优化银行保安👮‍♂️适合一般场景普通自动售货机🤖适合中等并发高并发自动售货机🚀适合高并发场景

6.5.5 实际应用建议

🎯 选择原则:

  1. 简单计数场景:优先考虑synchronized

    // 适合:访问量不大的计数器
    private int viewCount = 0;
    public synchronized void incrementView() {
        viewCount++;
    }
    
  2. 中等并发场景:选择AtomicInteger

    // 适合:中等并发的业务计数器
    private AtomicInteger orderCount = new AtomicInteger(0);
    public void createOrder() {
        orderCount.incrementAndGet();
    }
    
  3. 高并发场景:选择LongAdder

    // 适合:高并发的统计场景
    private LongAdder totalRequests = new LongAdder();
    public void handleRequest() {
        totalRequests.increment();
    }
    

⚠️ 注意事项:

  • LongAdder的最终一致性sum()方法返回的是近似值
  • 内存考虑:LongAdder会创建多个Cell对象,内存占用较高
  • 实时性要求:如果需要实时准确值,选择AtomicInteger
  • 业务场景:根据实际业务需求选择合适的锁机制

6.5.6 性能优化技巧

🔧 减少锁竞争:

// 不好:锁粒度太大
public synchronized void processData() {
    // 大量不需要同步的代码
    doHeavyWork();
    // 只有这一行需要同步
    count++;
}

// 好:锁粒度小
public void processData() {
    doHeavyWork();
    synchronized (this) {
        count++;
    }
}

🚀 使用无锁数据结构:

// 使用ConcurrentHashMap替代synchronized Map
private final Map<String, String> cache = new ConcurrentHashMap<>();

⚡ 批量操作优化:

// 批量累加,减少CAS操作次数
public void batchIncrement(int times) {
    for (int i = 0; i < times; i++) {
        adder.increment();
    }
}

7. 实战案例:高性能缓存实现

🏪 智能商店:高性能缓存的"商业案例"

7.1 需求分析:智能商店的"经营需求"

设计一个高性能的缓存系统,就像开一家智能商店🏪,要求:

需求 生活比喻 技术实现 重要性
支持并发读写 多个顾客同时购物 读写锁机制 ⭐⭐⭐⭐⭐
支持缓存过期 商品有保质期 时间戳管理 ⭐⭐⭐⭐
支持LRU淘汰 滞销商品下架 最近最少使用 ⭐⭐⭐⭐
线程安全 商店管理规范 锁机制保护 ⭐⭐⭐⭐⭐
高性能 快速结账 优化算法 ⭐⭐⭐⭐⭐

7.2 设计方案:智能商店的"技术架构"

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class HighPerformanceCache<K, V> {
    // 缓存数据存储:商店的货架
    private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();

    // 读写锁,用于保护LRU链表:商店的管理规则
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

    // LRU链表头尾节点:商品摆放顺序
    private volatile Node<K> head;
    private volatile Node<K> tail;

    // 缓存配置:商店的经营参数
    private final int maxSize;        // 最大商品数量
    private final long expireTime;    // 商品保质期
    private final AtomicInteger size = new AtomicInteger(0);  // 当前商品数量

    // 清理过期缓存的线程池:定期清理员工
    private final ScheduledExecutorService cleanupExecutor =
            Executors.newSingleThreadScheduledExecutor();

    public HighPerformanceCache(int maxSize, long expireTime) {
        this.maxSize = maxSize;
        this.expireTime = expireTime;

        // 初始化LRU链表:设置商品摆放区域
        head = new Node<>(null);
        tail = new Node<>(null);
        head.next = tail;
        tail.prev = head;

        // 启动清理任务:定期清理过期商品
        cleanupExecutor.scheduleAtFixedRate(this::cleanupExpired,
                1, 1, TimeUnit.SECONDS);
    }

    // 缓存条目:商店的商品
    private static class CacheEntry<V> {
        private final V value;                    // 商品内容
        private final long createTime;            // 商品上架时间
        private volatile long lastAccessTime;     // 最后访问时间

        public CacheEntry(V value) {
            this.value = value;
            this.createTime = System.currentTimeMillis();
            this.lastAccessTime = createTime;
        }

        public V getValue() {
            lastAccessTime = System.currentTimeMillis();  // 更新访问时间
            return value;
        }

        public boolean isExpired(long expireTime) {
            return System.currentTimeMillis() - createTime > expireTime;  // 检查是否过期
        }
    }

    // LRU链表节点:商品摆放位置
    private static class Node<K> {
        private K key;           // 商品标识
        private Node<K> prev;    // 前一个商品
        private Node<K> next;    // 后一个商品

        public Node(K key) {
            this.key = key;
        }
    }

    // 获取缓存:顾客购买商品
    public V get(K key) {
        CacheEntry<V> entry = cache.get(key);  // 查找商品
        if (entry == null) {
            return null;  // 商品不存在
        }

        if (entry.isExpired(expireTime)) {  // 检查商品是否过期
            remove(key);  // 移除过期商品
            return null;
        }

        // 更新LRU链表:将商品移到最前面
        updateLRU(key);

        return entry.getValue();  // 返回商品
    }

    // 设置缓存:商店上架商品
    public void put(K key, V value) {
        CacheEntry<V> newEntry = new CacheEntry<>(value);  // 创建新商品
        CacheEntry<V> oldEntry = cache.put(key, newEntry);  // 上架商品

        if (oldEntry == null) {
            // 新增条目:新商品上架
            int currentSize = size.incrementAndGet();
            if (currentSize > maxSize) {
                evictLRU();  // 商品太多,下架滞销商品
            }
        }

        // 更新LRU链表:将商品移到最前面
        updateLRU(key);
    }

    // 删除缓存:商店下架商品
    public V remove(K key) {
        CacheEntry<V> entry = cache.remove(key);  // 下架商品
        if (entry != null) {
            size.decrementAndGet();  // 减少商品数量
            removeFromLRU(key);      // 从摆放顺序中移除
        }
        return entry != null ? entry.getValue() : null;
    }

    // 更新LRU链表:调整商品摆放顺序
    private void updateLRU(K key) {
        writeLock.lock();  // 获取写锁(调整摆放顺序需要独占)
        try {
            // 先从链表中移除:从当前位置移除
            removeFromLRU(key);

            // 添加到头部:放到最前面(最近使用)
            Node<K> newNode = new Node<>(key);
            newNode.next = head.next;
            newNode.prev = head;
            head.next.prev = newNode;
            head.next = newNode;
        } finally {
            writeLock.unlock();
        }
    }

    // 从LRU链表中移除
    private void removeFromLRU(K key) {
        Node<K> current = head.next;
        while (current != tail) {
            if (current.key.equals(key)) {
                current.prev.next = current.next;
                current.next.prev = current.prev;
                break;
            }
            current = current.next;
        }
    }

    // LRU淘汰
    private void evictLRU() {
        writeLock.lock();
        try {
            if (tail.prev != head) {
                K keyToRemove = tail.prev.key;
                remove(keyToRemove);
            }
        } finally {
            writeLock.unlock();
        }
    }

    // 清理过期缓存
    private void cleanupExpired() {
        cache.entrySet().removeIf(entry -> {
            if (entry.getValue().isExpired(expireTime)) {
                size.decrementAndGet();
                removeFromLRU(entry.getKey());
                return true;
            }
            return false;
        });
    }

    // 获取缓存统计信息
    public CacheStats getStats() {
        return new CacheStats(size.get(), cache.size());
    }

    // 缓存统计信息
    public static class CacheStats {
        private final int size;
        private final int actualSize;

        public CacheStats(int size, int actualSize) {
            this.size = size;
            this.actualSize = actualSize;
        }

        public int getSize() {
            return size;
        }

        public int getActualSize() {
            return actualSize;
        }
    }

    // 关闭缓存
    public void shutdown() {
        cleanupExecutor.shutdown();
    }
}

7.3 性能测试

public class CachePerformanceTest {
    public static void main(String[] args) throws InterruptedException {
        HighPerformanceCache<String, String> cache =
                new HighPerformanceCache<>(1000, 5000);

        // 写入测试
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            cache.put("key" + i, "value" + i);
        }
        long writeTime = System.currentTimeMillis() - start;

        // 读取测试
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            cache.get("key" + i);
        }
        long readTime = System.currentTimeMillis() - start;

        // 并发测试
        start = System.currentTimeMillis();
        ExecutorService executor = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(1000);

        for (int i = 0; i < 1000; i++) {
            final int index = i;
            executor.submit(() -> {
                cache.put("concurrent_key" + index, "concurrent_value" + index);
                cache.get("concurrent_key" + index);
                latch.countDown();
            });
        }

        latch.await();
        long concurrentTime = System.currentTimeMillis() - start;

        System.out.println("写入时间: " + writeTime + "ms");
        System.out.println("读取时间: " + readTime + "ms");
        System.out.println("并发操作时间: " + concurrentTime + "ms");
        System.out.println("缓存统计: " + cache.getStats());

        cache.shutdown();
        executor.shutdown();
    }
}

8. 总结与最佳实践

8.1 锁机制选择指南

场景 推荐方案 原因
简单同步 synchronized 简单易用,JVM优化
需要超时 ReentrantLock 支持tryLock
读多写少 ReadWriteLock 提高读性能
计数器 AtomicInteger 无锁,高性能
累加器 LongAdder 高并发场景最优

8.2 性能优化建议

  1. 减少锁的粒度

    // 不好:锁整个方法
    public synchronized void method() {
        // 大量代码
    }
    
    // 好:只锁必要的代码块
    public void method() {
        // 不需要同步的代码
        synchronized(this) {
            // 需要同步的代码
        }
        // 不需要同步的代码
    }
    
  2. 使用无锁数据结构

    // 使用ConcurrentHashMap替代synchronized Map
    private final Map<String, String> cache = new ConcurrentHashMap<>();
    
  3. 避免锁嵌套

    // 避免死锁
    public void method1() {
        synchronized(lock1) {
            synchronized(lock2) {
                // 业务逻辑
            }
        }
    }
    

8.3 常见陷阱与解决方案

  1. volatile不保证原子性

    // 错误
    private volatile int count = 0;
    public void increment() {
        count++; // 不是原子操作
    }
    
    // 正确
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        count.incrementAndGet();
    }
    
  2. 忘记释放锁

    // 错误
    public void method() {
        lock.lock();
        // 如果这里抛异常,锁不会被释放
        doSomething();
        lock.unlock();
    }
    
    // 正确
    public void method() {
        lock.lock();
        try {
            doSomething();
        } finally {
            lock.unlock();
        }
    }
    
  3. 锁对象选择错误

    // 错误:使用String作为锁对象
    private final String lock = "lock";
    
    // 正确:使用专用对象
    private final Object lock = new Object();
    

8.4 监控与调试

  1. 使用JConsole监控线程状态
  2. 使用JProfiler分析锁竞争
  3. 使用Arthas诊断死锁
// 死锁检测示例
public class DeadlockDetector {
    public static void detectDeadlock() {
        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
        long[] deadlockedThreads = threadBean.findDeadlockedThreads();

        if (deadlockedThreads != null) {
            ThreadInfo[] threadInfos = threadBean.getThreadInfo(deadlockedThreads);
            for (ThreadInfo threadInfo : threadInfos) {
                System.out.println("死锁线程: " + threadInfo.getThreadName());
                System.out.println("等待锁: " + threadInfo.getLockName());
            }
        }
    }
}

8.5 学习资源推荐

8.5.1 官方文档与规范

📚 Oracle官方资源

🔍 OpenJDK源码

8.5.2 深度技术文章

🔬 锁机制深度解析

⚡ 性能优化相关

🏗️ 实战案例分析

8.5.3 开源项目学习

🚀 优秀开源项目

📖 源码学习建议

  • 从简单的AtomicInteger开始,理解CAS原理
  • 研究ConcurrentHashMap的实现,学习分段锁
  • 分析ThreadPoolExecutor源码,理解线程池机制

🎯 总结

本文深入解析了Java并发编程中的核心锁机制:

  1. synchronized:JVM内置锁,简单易用,适合大多数场景
  2. 锁升级:从偏向锁到重量级锁的优化过程
  3. volatile:轻量级同步,保证可见性和有序性
  4. ReentrantLock:可重入锁,提供更灵活的锁控制
  5. ReadWriteLock:读写分离,提高读性能
  6. 原子操作类:无锁编程,高性能计数器

通过实战案例,我们实现了一个高性能缓存系统,展示了各种锁机制的综合应用。