Java并发-锁机制
大约 4 分钟
Java并发-锁机制
核心理论
1.1 锁的基本概念
锁是并发编程中用于保证线程安全的核心机制,通过限制对共享资源的访问来避免竞态条件。Java提供了多种锁实现,从低级别的synchronized到高级别的Lock接口及其实现类。
1.2 锁的分类
- 悲观锁 vs 乐观锁:悲观锁假设冲突必然发生(如synchronized),乐观锁假设冲突很少发生(如CAS)
- 独占锁 vs 共享锁:独占锁仅允许一个线程访问(如ReentrantLock),共享锁允许多个线程同时访问(如ReadWriteLock的读锁)
- 可重入锁:允许线程重复获取已持有的锁(如synchronized和ReentrantLock)
- 公平锁 vs 非公平锁:公平锁按请求顺序获取锁,非公平锁允许插队获取锁
1.3 AQS框架原理
AbstractQueuedSynchronizer(AQS)是Java并发工具的基础框架,通过维护一个volatile int state和FIFO等待队列实现同步功能。自定义同步器只需重写tryAcquire/tryRelease等方法即可实现不同的同步语义。
代码实践
2.1 synchronized关键字使用场景
public class SynchronizedExample {
// 实例方法锁
public synchronized void instanceMethodLock() {
// 临界区代码
}
// 静态方法锁
public static synchronized void staticMethodLock() {
// 临界区代码
}
// 代码块锁
public void blockLock() {
synchronized (this) {
// 临界区代码
}
}
}
2.2 ReentrantLock实现生产者-消费者模型
public class ProducerConsumerWithLock {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 队列满时等待
}
queue.add(value);
System.out.println("生产: " + value);
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 队列空时等待
}
int value = queue.poll();
System.out.println("消费: " + value);
notFull.signal(); // 唤醒生产者
return value;
} finally {
lock.unlock();
}
}
}
2.3 读写锁优化并发读性能
public class ReadWriteLockExample {
private final Map<String, Object> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public Object get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}
public void put(String key, Object value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
}
设计思想
3.1 锁优化技术
- 偏向锁:减少无竞争情况下的锁开销,只在第一次获取锁时设置线程ID
- 轻量级锁:通过CAS操作尝试获取锁,避免重量级锁的系统调用
- 重量级锁:通过操作系统互斥量实现,适用于多线程竞争场景
- 锁消除:JVM自动消除不可能存在竞争的锁
- 锁粗化:将多个连续的细粒度锁合并为一个粗粒度锁
3.2 无锁编程思想
无锁编程通过CAS操作避免使用传统锁,提高并发性能。Java中的原子类(如AtomicInteger)和ConcurrentHashMap都采用了无锁设计。
避坑指南
4.1 死锁的产生与避免
死锁四要素:互斥条件、请求与保持条件、不可剥夺条件、循环等待条件。避免死锁的方法:
- 按固定顺序获取锁
- 使用tryLock()设置超时
- 使用LockSupport中断线程
4.2 synchronized与Lock的选择
- 简单场景优先使用synchronized(JVM优化更成熟)
- 需要灵活功能(如中断、超时、公平锁)时使用Lock
- 读多写少场景使用ReadWriteLock提高并发性
4.3 锁的过度使用
避免对不需要同步的代码加锁,过度同步会导致性能下降。可以通过不可变对象、ThreadLocal等方式减少锁竞争。
深度思考题
- synchronized的实现原理是什么?JVM对synchronized做了哪些优化?
- ReentrantLock和synchronized的区别是什么?各自适用场景?
- 什么是锁降级?为什么ReadWriteLock支持锁降级而不支持锁升级?
思考题回答:
synchronized基于对象头的Mark Word实现,JVM通过对象监视器(monitor)实现锁机制。优化包括:偏向锁、轻量级锁、重量级锁、锁消除和锁粗化。
区别:ReentrantLock支持中断、超时、公平锁和条件变量,需要手动释放锁;synchronized是隐式锁,由JVM自动管理。适用场景:简单同步用synchronized,需要高级功能用ReentrantLock。
锁降级是指从写锁降级为读锁,可在持有写锁时获取读锁再释放写锁。ReadWriteLock不支持锁升级(读锁升级为写锁),因为可能导致死锁:多个读锁同时尝试升级为写锁时互相等待。