第一部分:Lock接口基础

理解Lock接口的核心功能与优势

学习目标

掌握Lock接口的核心方法、使用场景以及与synchronized的区别

synchronized的局限性

内置锁synchronized存在以下限制:

  • 不可中断等待
  • 不支持超时获取锁
  • 无法实现公平锁
  • 只能有一个等待队列
  • 无法尝试获取锁

Lock接口的优势

Lock接口提供了更灵活的锁机制:

  • 可中断锁获取
  • 支持超时获取锁
  • 可实现公平锁
  • 多个条件队列
  • 尝试获取锁机制
锁定状态
未锁定状态
Lock接口方法 描述 使用场景
lock() 获取锁,如果锁不可用则阻塞 基本锁获取
unlock() 释放锁 必须在finally块中调用
tryLock() 尝试获取锁,立即返回结果 避免死锁
tryLock(long, TimeUnit) 超时尝试获取锁 避免长时间阻塞
lockInterruptibly() 可中断获取锁 响应中断
newCondition() 创建新的Condition对象 实现精细等待/通知

Lock接口使用原则

1. 必须在try-finally块中使用Lock
2. 在try块前获取锁
3. 在finally块中释放锁
4. 避免在锁内调用外部方法
5. 使用tryLock避免死锁

第二部分:ReentrantLock使用

掌握ReentrantLock的基本使用和高级特性

学习目标

理解ReentrantLock的可重入性、公平锁和非公平锁的区别

基本使用示例

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private final Lock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); // 获取锁 try { count++; } finally { lock.unlock(); // 确保释放锁 } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }

公平锁与非公平锁

ReentrantLock支持公平锁和非公平锁两种模式:

// 创建非公平锁(默认) Lock nonFairLock = new ReentrantLock(); // 创建公平锁 Lock fairLock = new ReentrantLock(true);

公平锁: 按照线程请求顺序获取锁

非公平锁: 允许插队,性能更高

tryLock使用示例

避免死锁和长时间阻塞:

public void transfer(Account from, Account to, int amount) { if (from.lock.tryLock()) { try { if (to.lock.tryLock()) { try { from.withdraw(amount); to.deposit(amount); } finally { to.lock.unlock(); } } } finally { from.lock.unlock(); } } }
线程1: 获取锁 → 执行操作 → 释放锁
线程2: 尝试获取锁 → 成功 → 执行操作 → 释放锁
线程3: 尝试获取锁 → 失败 → 执行其他操作

第三部分:Condition应用

使用Condition实现精细的线程等待/通知机制

学习目标

掌握Condition接口的使用方法,实现生产者-消费者模式

Condition核心方法

  • await() - 使当前线程等待
  • signal() - 唤醒一个等待线程
  • signalAll() - 唤醒所有等待线程
  • awaitUninterruptibly() - 不可中断等待
  • await(long, TimeUnit) - 超时等待

与Object监视器对比

  • Condition可创建多个等待队列
  • 更精细的线程控制
  • 支持超时和中断
  • 需要配合Lock使用

生产者-消费者模式实现

import java.util.concurrent.locks.*; public class BoundedBuffer { private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private final Object[] items = new Object[100]; private int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); // 等待不满 items[putptr] = x; if (++putptr == items.length) putptr = 0; count++; notEmpty.signal(); // 通知不空 } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); // 等待不空 Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; count--; notFull.signal(); // 通知不满 return x; } finally { lock.unlock(); } } }
生产者: 等待不满条件 → 生产 → 通知不空
消费者: 等待不空条件 → 消费 → 通知不满

第四部分:读写锁

使用ReentrantReadWriteLock提高并发性能

学习目标

理解读写分离原理,掌握ReentrantReadWriteLock的使用

读写锁特性

  • 读锁共享:多个线程可同时持有读锁
  • 写锁独占:同一时间只有一个线程可持有写锁
  • 写锁优先:写锁请求优先于读锁
  • 锁降级:写锁可降级为读锁

适用场景

  • 读多写少的场景
  • 缓存系统
  • 配置信息读取
  • 资源池管理

缓存系统实现

import java.util.concurrent.locks.*; public class Cache { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock readLock = rwl.readLock(); private final Lock writeLock = rwl.writeLock(); private final Map map = new HashMap<>(); public V get(K key) { readLock.lock(); try { return map.get(key); } finally { readLock.unlock(); } } public void put(K key, V value) { writeLock.lock(); try { map.put(key, value); } finally { writeLock.unlock(); } } public V putIfAbsent(K key, V value) { writeLock.lock(); try { V v = map.get(key); if (v == null) { map.put(key, value); return null; } return v; } finally { writeLock.unlock(); } } }
读线程1: 获取读锁 → 读取数据 → 释放读锁
读线程2: 获取读锁 → 读取数据 → 释放读锁
写线程: 等待读锁释放 → 获取写锁 → 写入数据 → 释放写锁

第五部分:常见错误与陷阱

避免Lock使用中的常见问题

1. 忘记释放锁

未在finally块中释放锁导致死锁

// 错误示例:忘记释放锁 public void method() { lock.lock(); try { // 执行操作 } catch (Exception e) { // 处理异常 } // 缺少 unlock() }

正确做法:在finally块中释放锁

2. 锁嵌套问题

嵌套获取多个锁可能导致死锁

// 错误示例:锁嵌套顺序不一致 public void transfer(Account a, Account b) { a.lock.lock(); b.lock.lock(); // 可能死锁 // ... b.lock.unlock(); a.lock.unlock(); } public void reverseTransfer(Account a, Account b) { b.lock.lock(); a.lock.lock(); // 顺序不一致导致死锁 // ... }

解决方案:按固定顺序获取锁

3. 条件等待错误

在条件等待前未检查条件

// 错误示例:未检查条件 public void method() { lock.lock(); try { condition.await(); // 可能错过信号 // 执行操作 } finally { lock.unlock(); } }

正确做法:使用while循环检查条件

4. 锁粒度过大

锁范围过大降低并发性能

// 错误示例:锁范围过大 public void process() { lock.lock(); try { // 非临界区操作(耗时) loadData(); // 临界区操作 updateCounter(); // 非临界区操作 saveResult(); } finally { lock.unlock(); } }

解决方案:只锁真正需要同步的部分

5. 读写锁误用

在写操作中使用读锁

// 错误示例:写操作使用读锁 public void updateValue() { readLock.lock(); // 错误:应该用写锁 try { value = newValue; // 写操作 } finally { readLock.unlock(); } }

写操作必须使用写锁

6. 锁中断处理不当

未正确处理中断异常

// 错误示例:忽略中断异常 public void method() { try { lock.lockInterruptibly(); // ... } catch (InterruptedException e) { // 忽略中断 } finally { lock.unlock(); } }

正确做法:恢复中断状态或处理中断

第六部分:Lock最佳实践

高效、安全地使用Lock接口

Lock优点

  • 更灵活的锁机制
  • 支持中断和超时
  • 可实现公平锁
  • 多个条件队列
  • 读写锁提高性能

Lock缺点

  • 需要手动释放锁
  • 使用不当容易死锁
  • 代码复杂度增加
  • 性能开销略大

最佳实践1:锁释放保证

  • 始终在finally块中释放锁
  • 避免在锁内抛出未处理异常
  • 锁获取和释放配对使用
Lock lock = new ReentrantLock(); public void safeMethod() { lock.lock(); try { // 临界区代码 } finally { lock.unlock(); } }

最佳实践2:避免死锁

  • 使用tryLock避免死锁
  • 按固定顺序获取多个锁
  • 设置锁获取超时时间
public void safeTransfer(Account a, Account b) { while (true) { if (a.lock.tryLock()) { try { if (b.lock.tryLock()) { try { // 执行转账 return; } finally { b.lock.unlock(); } } } finally { a.lock.unlock(); } } // 随机等待避免活锁 Thread.sleep(random.nextInt(100)); } }

最佳实践3:读写锁优化

  • 读多写少场景使用读写锁
  • 写锁降级提高性能
  • 避免读锁升级为写锁
// 写锁降级示例 public void processData() { writeLock.lock(); try { // 更新数据 updateData(); // 降级为读锁 readLock.lock(); } finally { writeLock.unlock(); // 释放写锁,保留读锁 } try { // 使用读锁读取数据 readData(); } finally { readLock.unlock(); } }

性能优化建议

1. 优先使用tryLock避免阻塞
2. 读写分离场景使用ReentrantReadWriteLock
3. 减少锁持有时间
4. 避免锁嵌套
5. 合理选择公平锁/非公平锁

第七部分:练习与测验

通过练习巩固Lock知识

动手练习

  • 使用ReentrantLock实现线程安全的计数器
  • 使用Condition实现生产者-消费者模式
  • 使用ReentrantReadWriteLock实现缓存系统
  • 使用tryLock解决转账死锁问题
1

Lock知识测验

以下关于ReentrantLock的描述,哪项是错误的?
使用Lock接口时,释放锁的正确位置是?

深入学习资源

1. 《Java并发编程实战》第13章
2. Oracle官方文档:java.util.concurrent.locks
3. ReentrantLock源码分析
4. Java并发编程之美:Lock与Condition