
在 Java 并发编程中锁是解决线程安全问题的核心工具。从原生的synchronized关键字到 JUC 包提供的Lock接口实现类锁的设计越来越灵活能适配不同的业务场景。本文将重点讲解ReentrantLock可重入锁和ReentrantReadWriteLock读写锁的核心特性、使用场景及与synchronized的区别帮助开发者理解并灵活运用这些锁机制。一、ReentrantLock灵活的可重入独占锁ReentrantLock是 JUC 中对Lock接口的经典实现具备与synchronized相同的 “可重入” 特性但功能更丰富、使用更灵活是并发编程中替代synchronized的重要选择。1.1 核心特性可重入性可重入锁又名递归锁是指同一线程在外层方法获取锁后进入内层方法时会自动获取该锁无需重新竞争可有效避免死锁。1.1.1 synchronized 实现可重入自动加解锁public class ReentrantLockDemo { public synchronized void a(){ this.b(); // 内层方法自动获取锁 System.out.println(a); } public synchronized void b(){ System.out.println(b); } public static void main(String[] args) { new ReentrantLockDemo().a(); // 输出b → a } }1.1.2 ReentrantLock 实现可重入手动加解锁public class ReentrantLockDemo2 { private final ReentrantLock lock new ReentrantLock(); public void a(){ lock.lock(); // 外层加锁 this.b(); // 内层自动获取锁 System.out.println(a); lock.unlock(); // 外层解锁 } public void b(){ lock.lock(); // 内层加锁无需等待 System.out.println(b); lock.unlock(); // 内层解锁 } public static void main(String[] args) { new ReentrantLockDemo2().a(); // 输出b → a } }关键注意ReentrantLock 的加解锁需手动配对加锁次数与解锁次数必须一致否则会导致其他线程无法获取锁。1.2 公平锁 vs 非公平锁ReentrantLock 支持公平 / 非公平两种模式而synchronized仅支持非公平锁非公平锁默认线程竞争锁时允许 “插队”谁抢到就是谁的效率高但可能导致线程饥饿公平锁按线程等待时间排序排队最久的线程优先获取锁无饥饿问题但牺牲吞吐量。公平锁创建方式// 构造参数传入true表示公平锁 private final ReentrantLock lock new ReentrantLock(true);1.3 限时等待解决死锁的关键特性ReentrantLock 提供tryLock()方法实现限时等待获取锁而synchronized获取不到锁会一直阻塞无超时机制。tryLock()立即返回锁申请结果true 成功false 失败tryLock(long timeout, TimeUnit unit)等待指定时间后返回结果。1.3.1 死锁案例synchronized 无法解决public class DeadlockDemo { private static final Object lock1 new Object(); private static final Object lock2 new Object(); public static void main(String[] args) { // 线程1持有lock1等待lock2 new Thread(() - { synchronized (lock1) { try { Thread.sleep(500); } catch (InterruptedException e) {} synchronized (lock2) {} // 永远等待 } }).start(); // 线程2持有lock2等待lock1 new Thread(() - { synchronized (lock2) { try { Thread.sleep(500); } catch (InterruptedException e) {} synchronized (lock1) {} // 永远等待 } }).start(); } }1.3.2 ReentrantLock 解决死锁限时等待public class DeadlockDemo2 { private static ReentrantLock lock1 new ReentrantLock(); private static ReentrantLock lock2 new ReentrantLock(); public static void main(String[] args) { // 线程1尝试获取lock1再尝试获取lock2失败则放弃 new Thread(() - { boolean getLock1 lock1.tryLock(); if (getLock1) { try { Thread.sleep(500); boolean getLock2 lock2.tryLock(); if (getLock2) lock2.unlock(); else System.out.println(线程1未获取到lock2放弃); } catch (InterruptedException e) {} finally { lock1.unlock(); } } }).start(); // 线程2逻辑同上 new Thread(() - { boolean getLock2 lock2.tryLock(); if (getLock2) { try { Thread.sleep(500); boolean getLock1 lock1.tryLock(); if (getLock1) lock1.unlock(); else System.out.println(线程2未获取到lock1放弃); } catch (InterruptedException e) {} finally { lock2.unlock(); } } }).start(); } }1.4 ReentrantLock vs synchronized核心区别特性synchronizedReentrantLock加解锁方式自动进行手动进行lock ()/unlock ()可重入性支持支持需手动配对加解锁次数响应中断不支持一直阻塞支持tryLock () 限时等待公平锁不支持仅非公平支持构造参数指定灵活性低固定逻辑高可配合 Condition 精准唤醒二、ReentrantReadWriteLock读多写少场景的最优解ReentrantLock 和synchronized都是独占锁同一时刻仅一个线程获取锁但在 “读多写少” 场景下独占锁会导致读操作串行执行效率极低。ReentrantReadWriteLock读写锁将锁拆分为 “读锁” 和 “写锁”适配该场景。2.1 读写锁核心规则读写锁的核心是 “分离读和写”遵循以下规则写写互斥多个写线程不能同时执行读写 / 写读互斥写线程执行时读线程阻塞反之亦然读读共享多个读线程可同时执行无需竞争。2.2 无锁缓存的线程安全问题未加锁的缓存会导致写操作原子性丢失读操作获取到不完整数据class MyCache{ private MapString, String cache new HashMap(); // 写操作无锁原子性丢失 public void put(String key, String value){ try { System.out.println(Thread.currentThread().getName() 开始写入); Thread.sleep(300); cache.put(key, value); System.out.println(Thread.currentThread().getName() 写入成功); } catch (InterruptedException e) {} } // 读操作无锁可能读到空值 public void get(String key){ try { System.out.println(Thread.currentThread().getName() 开始读出); Thread.sleep(300); String value cache.get(key); System.out.println(Thread.currentThread().getName() 读出成功 value); } catch (InterruptedException e) {} } }2.3 读写锁改造缓存保证安全 提升效率class MyCache{ // 初始化读写锁 public final ReentrantReadWriteLock rwLock new ReentrantReadWriteLock(); private MapString, String cache new HashMap(); // 写操作加写锁独占 public void put(String key, String value){ rwLock.writeLock().lock(); // 加写锁 try { System.out.println(Thread.currentThread().getName() 开始写入); Thread.sleep(300); cache.put(key, value); System.out.println(Thread.currentThread().getName() 写入成功); } catch (InterruptedException e) {} finally { rwLock.writeLock().unlock(); // 释放写锁 } } // 读操作加读锁共享 public void get(String key){ rwLock.readLock().lock(); // 加读锁 try { System.out.println(Thread.currentThread().getName() 开始读出); Thread.sleep(300); String value cache.get(key); System.out.println(Thread.currentThread().getName() 读出成功 value); } catch (InterruptedException e) {} finally { rwLock.readLock().unlock(); // 释放读锁 } } }改造后效果写线程串行执行保证原子性读线程并行执行提升读效率读写 / 写读互斥保证数据一致性。2.4 锁降级写锁→读锁的安全转换锁降级是指当前线程持有写锁时先获取读锁再释放写锁目的是保证 “写后读” 的数据一致性。2.4.1 正确的锁降级public void lockDowngrade() { ReentrantReadWriteLock rwLock new ReentrantReadWriteLock(); // 1. 获取写锁 rwLock.writeLock().lock(); System.out.println(获取写锁执行写操作); // 2. 先获取读锁关键保证写锁释放前已持有读锁 rwLock.readLock().lock(); System.out.println(获取读锁开始锁降级); // 3. 释放写锁 rwLock.writeLock().unlock(); System.out.println(释放写锁仅保留读锁); // 4. 执行读操作此时其他读线程可并行 // 5. 释放读锁 rwLock.readLock().unlock(); }2.4.2 错误的锁降级数据不一致public void wrongDowngrade() { ReentrantReadWriteLock rwLock new ReentrantReadWriteLock(); rwLock.writeLock().lock(); System.out.println(获取写锁执行写操作); // 错误先释放写锁再获取读锁中间可能被其他写线程插队 rwLock.writeLock().unlock(); rwLock.readLock().lock(); // 此时数据可能已被修改 rwLock.readLock().unlock(); }关键注意读写锁不支持锁升级读锁无法直接升级为写锁避免读线程并发时写操作破坏数据一致性。2.5 读写锁的 “饥饿” 问题在读线程极多、写线程极少的场景下读锁会一直被占用写线程永远无法获取写锁即 “写线程饥饿”。缓解方案使用公平锁模式按等待时间排序但会牺牲读操作的并发吞吐量本质原因读锁的共享特性导致写锁获取条件无读锁 无写锁难以满足。2.6 读写锁核心特性总结支持公平 / 非公平策略支持可重入读锁可重复获取读锁写锁可重复获取写锁 / 读锁支持锁降级写→读不支持锁升级读→写读多写少场景下效率远高于独占锁但需注意写线程饥饿问题。三、总结ReentrantLock是灵活的独占锁支持可重入、公平锁、限时等待可解决synchronized无法处理的死锁、中断等问题ReentrantReadWriteLock适配读多写少场景通过 “读读共享、写写 / 读写互斥” 平衡安全性和效率锁降级是读写锁的重要优化手段需遵循 “先获取读锁、再释放写锁” 的规则选择锁的核心原则读多写少用读写锁普通场景用 ReentrantLock简单场景用 synchronized。合理选择锁机制既能保证线程安全又能最大化并发效率是 Java 并发编程的核心能力。