一、Java 锁机制的核心分类
Java 中的锁主要分为内置锁(synchronized) 和显式锁(Lock 接口) 两大类,衍生出多种适配不同场景的锁类型,先看核心分类框架:
Java锁机制
内置锁 synchronized
显式锁 Lock接口
偏向锁
轻量级锁
重量级锁
ReentrantLock 可重入锁
ReentrantReadWriteLock 读写锁
StampedLock 戳记锁
其他特殊锁
公平锁/非公平锁
可重入锁/不可重入锁
乐观锁/悲观锁
二、核心锁类型详解(附代码示例)
1. 内置锁:synchronized(最基础、易用)
核心特点 :JVM 层面实现的隐式锁,自动加锁 / 释放锁,可重入,从 JDK 1.6 开始引入偏向锁、轻量级锁优化,避免无脑升级为重量级锁。适用场景:简单并发场景,代码简洁,无需手动释放锁。
代码示例:
java
运行
public class SynchronizedDemo {
// 1. 修饰方法(锁对象是当前实例)
public synchronized void methodLock() {
System.out.println("方法级锁:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 2. 修饰静态方法(锁对象是类的Class对象)
public static synchronized void staticMethodLock() {
System.out.println("静态方法级锁:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 3. 修饰代码块(自定义锁对象)
public void blockLock() {
Object lock = new Object();
synchronized (lock) {
System.out.println("代码块锁:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
// 测试方法锁(多线程竞争同一实例锁)
new Thread(demo::methodLock, "线程1").start();
new Thread(demo::methodLock, "线程2").start();
// 测试静态方法锁
new Thread(SynchronizedDemo::staticMethodLock, "线程3").start();
new Thread(SynchronizedDemo::staticMethodLock, "线程4").start();
}
}
执行结果 :同一类型的锁会串行执行,比如线程 1 执行完 methodLock 后,线程 2 才会执行。
2. 显式锁:ReentrantLock(灵活可控)
核心特点 :JUC 包下的显式锁,需手动 lock() 加锁、unlock() 释放锁(建议放 finally 块),支持公平锁 / 非公平锁、可中断、超时获取锁。适用场景:复杂并发场景,需要灵活控制锁的获取 / 释放。
代码示例:
java
运行
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
// 创建非公平锁(默认),公平锁传 true:new ReentrantLock(true)
private final ReentrantLock lock = new ReentrantLock();
public void doTask() {
// 加锁
lock.lock();
try {
System.out.println("ReentrantLock加锁:" + Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁(必须放finally,避免死锁)
lock.unlock();
System.out.println("ReentrantLock释放锁:" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
new Thread(demo::doTask, "线程A").start();
new Thread(demo::doTask, "线程B").start();
}
}
3. 读写锁:ReentrantReadWriteLock(读写分离,提升并发)
核心特点 :分为读锁(共享锁)和写锁(排他锁),多个读线程可同时获取读锁,写线程独占锁,解决 "多读少写" 场景的性能问题。适用场景:缓存、配置读取等多读少写的场景。
代码示例:
java
运行
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁(共享)
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
// 写锁(排他)
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 读操作:加读锁
public String get(String key) {
readLock.lock();
try {
System.out.println("读锁:" + Thread.currentThread().getName() + " 读取key=" + key);
return cache.get(key);
} finally {
readLock.unlock();
}
}
// 写操作:加写锁
public void put(String key, String value) {
writeLock.lock();
try {
System.out.println("写锁:" + Thread.currentThread().getName() + " 写入key=" + key);
cache.put(key, value);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
// 多个读线程可同时执行
new Thread(() -> demo.get("name"), "读线程1").start();
new Thread(() -> demo.get("name"), "读线程2").start();
// 写线程独占,读线程需等待写锁释放
new Thread(() -> demo.put("name", "Java锁机制"), "写线程1").start();
new Thread(() -> demo.get("name"), "读线程3").start();
}
}
执行结果:读线程 1、2 同时执行,写线程 1 执行时,所有读 / 写线程等待,写锁释放后读线程 3 执行。
4. 乐观锁 vs 悲观锁(思想层面)
- 悲观锁:认为每次操作都会竞争,先加锁再执行(synchronized、ReentrantLock 都是),适合写多读少场景。
- 乐观锁:认为不会有竞争,不加锁,通过 CAS(Compare And Swap)机制保证原子性,适合读多写少场景(如 AtomicInteger、数据库版本号)。
乐观锁代码示例(AtomicInteger):
java
运行
import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockDemo {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
// CAS 原子操作:比较并替换
count.incrementAndGet();
System.out.println(Thread.currentThread().getName() + " count=" + count.get());
}
public static void main(String[] args) {
OptimisticLockDemo demo = new OptimisticLockDemo();
for (int i = 0; i < 5; i++) {
new Thread(demo::increment, "线程" + i).start();
}
}
}
三、关键锁特性对比
| 锁类型 | 核心优势 | 缺点 | 适用场景 |
|---|---|---|---|
| synchronized | 简洁、自动释放、JVM 优化 | 灵活性低、无法中断 | 简单并发、低复杂度场景 |
| ReentrantLock | 灵活、可中断、公平 / 非公平 | 需手动释放、代码稍复杂 | 复杂并发、需精细控制锁 |
| ReentrantReadWriteLock | 读写分离、高并发读 | 写锁独占、易产生写饥饿 | 多读少写(缓存、配置) |
| 乐观锁(CAS) | 无锁竞争、性能高 | 高并发下 CAS 失败重试耗 CPU | 读多写少、低冲突场景 |
四、锁机制避坑要点
- 死锁:多个线程互相持有对方需要的锁,避免方式:按固定顺序加锁、设置锁超时、使用 tryLock ()。
- 锁升级:synchronized 会从偏向锁→轻量级锁→重量级锁升级,不可逆,高并发下尽量减少锁竞争。
- 锁粒度:避免锁范围过大(如整个方法加锁),尽量缩小锁代码块,只锁关键逻辑。
总结
- 基础场景用 synchronized:代码简洁,JVM 优化足够应对大部分简单并发,无需手动管理锁。
- 复杂场景用 ReentrantLock / 读写锁:需要中断、超时、公平锁或读写分离时,选择显式锁更灵活