Java锁机制完全拆解:从synchronized到分布式锁的终极生存指南


——高并发场景下,如何用一把锁守住你的代码贞操?

一、锁的本质:多线程世界的“道德与法治”

当多个线程同时抢夺资源时,锁就是维持秩序的审判者:

经典翻车现场:

10个线程同时给账户余额+100,结果只加了200

库存超卖:100人抢购90件商品,系统显示-10库存

锁的核心使命:

原子性:把“余额=余额+100”变成不可分割的操作

可见性:强制线程从主内存读取最新值,而非缓存副本

有序性:禁止指令重排序引发的诡异BUG

二、单机锁宇宙:从JVM锁到CAS无锁编程

1. synchronized:原始但强大的监视器锁

java

Copy Code

// 对象锁(锁住this实例)

public synchronized void transfer() {

// 临界区代码

}

// 类锁(锁住Class对象)

public static synchronized void staticMethod() {

// 临界区代码

}

// 同步代码块(锁住任意对象)

Object lock = new Object();

public void method() {

synchronized(lock) {

// 临界区代码

}

}

升级路线:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(涉及对象头Mark Word操作)

锁膨胀代价:重量级锁需要CPU从用户态切换到内核态,耗时约1微秒

2. ReentrantLock:可定制的显式锁

java

Copy Code

Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();

void method() {

lock.lock();

try {

while(!conditionMet) {

condition.await(); // 释放锁并等待

}

// 临界区代码

condition.signalAll();

} finally {

lock.unlock();

}

}

性能对比:

场景 synchronized ReentrantLock

低竞争 更快 稍慢

高竞争 可能死锁 支持公平锁

超时等待 不支持 tryLock(3, TimeUnit.SECONDS)

3. CAS无锁编程:Compare And Swap的魔法

java

Copy Code

AtomicInteger balance = new AtomicInteger(100);

// 无锁实现余额增减

public void add(int delta) {

while(true) {

int oldVal = balance.get();

int newVal = oldVal + delta;

if(balance.compareAndSet(oldVal, newVal)) {

break;

}

}

}

ABA问题解决方案:使用AtomicStampedReference带版本号

三、高阶锁策略:应对复杂战场

1. 读写锁(ReadWriteLock)

java

Copy Code

ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

Lock readLock = rwLock.readLock();

Lock writeLock = rwLock.writeLock();

// 读操作(共享锁)

void getData() {

readLock.lock();

try { /* 读取操作 */ }

finally { readLock.unlock(); }

}

// 写操作(独占锁)

void updateData() {

writeLock.lock();

try { /* 写入操作 */ }

finally { writeLock.unlock(); }

}

适用场景:读多写少(如缓存系统)

2. 自旋锁(SpinLock)

java

Copy Code

public class SpinLock {

private AtomicBoolean locked = new AtomicBoolean(false);

public void lock() {

while(!locked.compareAndSet(false, true)) {

// 自旋等待(适合短时间锁定)

}

}

public void unlock() {

locked.set(false);

}

}

优化技巧:JDK的LongAdder就是自旋锁+CAS的产物

3. 分段锁(Segment Lock)

ConcurrentHashMap实现精髓:

java

Copy Code

// 默认分成16个Segment,每个Segment独立加锁

static final class Segment extends ReentrantLock {

volatile HashEntry[] table;

}

降低锁粒度:把全局锁变成多个局部锁,减少竞争

四、分布式锁:跨JVM的生死狙击

1. Redis分布式锁(Redisson实现)

java

Copy Code

RLock lock = redisson.getLock("orderLock");

lock.lock();

try {

// 业务代码

} finally {

lock.unlock();

}

核心机制:

加锁Lua脚本保证原子性

WatchDog自动续期防止锁过期

红锁算法(RedLock)应对主从故障

2. Zookeeper分布式锁

java

Copy Code

InterProcessMutex lock = new InterProcessMutex(client, "/locks/order");

lock.acquire();

try {

// 业务代码

} finally {

lock.release();

}

底层原理:

创建临时有序节点

判断自己是否是最小节点

监听前序节点删除事件

3. 分布式锁选型对比

维度 Redis Zookeeper

性能 高(内存操作) 中(需要集群协调)

一致性 AP模型(可能丢锁) CP模型(强一致)

实现复杂度 简单 复杂(需处理会话)

五、锁优化与排错:从理论到实战

1. 性能优化四板斧

减少锁粒度:用ConcurrentHashMap代替
Collections.synchronizedMap

降低锁时间:把与共享资源无关的代码移出同步块

锁分离策略:读写锁分离,写时复制(CopyOnWrite)

无锁数据结构:LongAdder替代AtomicLong

2. 死锁排查工具箱

jstack:

bash

Copy Code

jstack -l | grep "deadlock"

Arthas:

bash

Copy Code

thread -b # 自动检测死锁

代码规范:

按固定顺序获取锁

使用tryLock()设置超时时间

六、锁的哲学:何时不用锁?

1. 无锁编程的终极挑战

ThreadLocal:线程封闭(每个线程独立副本)

不变性模式:使用final不可变对象

Actor模型:Akka框架的消息信箱机制

2. 分布式系统新思路

CAS+版本号:ETCD的事务机制

CRDT无冲突复制数据类型:适用于最终一致性场景

七、灵魂拷问:你的锁真的安全吗?

synchronized锁String常量池:不同业务可能共享同一把锁

锁对象被修改:lock = new Object()导致锁失效

锁异常未释放:lock.lock()后代码抛异常未进finally块

《Java并发编程实战》作者Brian Goetz警告:

“并发BUG就像薛定谔的猫,你永远不知道何时会跳出来挠你”

本文配套速查表:

锁选择决策树:

单机低竞争 → synchronized

需要超时/公平锁 → ReentrantLock

读多写少 → ReadWriteLock

跨JVM → Redisson/Zookeeper

死锁预防口诀:

“一锁一线程,顺序要固定,超时不能忘,监控必须上”

( 警告:未经严格测试的锁策略可能导致线上血案,请谨慎操作)

相关文章

Java微服务架构选型:优雅拆分与高效整合

Java微服务架构选型:优雅拆分与高效整合在当今的软件开发领域,微服务架构已经成为一种主流趋势。它将单一庞大的应用程序划分为多个小型、自治的服务,每个服务负责特定的功能模块。对于使用Java语言的开发...

Java 9 到 Java 16 的版本演进:一次模块化革命和语言的持续进化

Java 9 到 Java 16 的版本演进:一次模块化革命和语言的持续进化在这个飞速发展的时代,Java 的发布节奏也变得越来越快,从传统的“龟速”升级到如今的“火车模式”。从 2017 年 9 月...

Java 拆分PDF页面

在操作PDF文档时,拆分PDF页面,意味着将一页的文本内容展示在多个较小些的页面上。Free Spire.PDF for Java控件支持从水平或垂直方向来将PDF页面拆分为多个页面。本文将演示相关代...

项目案例:Java多线程批量拆分List导入数据库

一、前言二、直接把list怼进Mysql三、分组把list导入Mysql中四、多线程分批导入Mysql五、小结一、前言前两天做了一个导入的功能,导入开始的时候非常慢,导入2w条数据要1分多钟,后来一点...

value中存储过多的元素-Redis大key多key拆分方案

背景在我的项目中,会存在一个DG下拥有10w+的学生,每个学生在进入直播之前,都需要通过校验,查询是否是这个直播所关联DG下的学生;为了提高并发,我们把大纲和学生的关系存入Redis中,使用set存储...

如何将长字符串定义拆分成多行?

在编程的过程中,我们常常会遇到需要处理长字符串的情况。当字符串特别长时,将其全部写在一行会让代码变得难以阅读和维护。那么,怎样才能把长字符串的定义拆分成多行呢?这是不少开发者都会遇到并想要解决的问题。...