ReentrantLock与公平锁非公平锁
ReentrantLock 相比 synchronized 更灵活,但也更容易用错。很多人知道它支持公平锁和非公平锁,却不知道如何选择。本文重点讲使用场景、注意事项以及两者如何选择。
ReentrantLock vs synchronized
| 特性 |
ReentrantLock |
synchronized |
| 锁的获取方式 |
显式 lock/unlock |
隐式,JVM管理 |
| 公平性 |
支持公平/非公平 |
非公平 |
| 可中断 |
支持 |
不支持 |
| 超时获取 |
支持 |
不支持 |
| 条件变量 |
支持多个Condition |
一个隐式条件 |
| 性能 |
JDK6+ 近似 |
优化后近似 |
| 代码复杂度 |
较高 |
较低 |
基本用法
public class Counter { private final ReentrantLock lock = new ReentrantLock(); private int count; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } }
|
注意:unlock() 必须在 finally 块中调用,否则异常时锁不会释放。
公平锁与非公平锁
#
创建方式
ReentrantLock lock = new ReentrantLock(); ReentrantLock lock = new ReentrantLock(false);
ReentrantLock fairLock = new ReentrantLock(true);
|
#
非公平锁
lock.lock();
if (lock.tryLock()) { }
|
特点:
- 允许”插队”,后到的线程可能先获取锁
- 吞吐量大,性能更好
- 可能导致线程饥饿
#
公平锁
特点:
#
性能对比
public class FairnessTest { private static final int THREAD_COUNT = 10; private static final int ITERATIONS = 100000; public static void main(String[] args) { testLock(new ReentrantLock(false), "非公平锁"); testLock(new ReentrantLock(true), "公平锁"); } }
|
结果:非公平锁吞吐量通常比公平锁高 5~10 倍。
建议:除非有明确的公平性需求,否则使用非公平锁。
可中断锁
public void interruptibleLock() { try { lock.lockInterruptibly(); try { } finally { lock.unlock(); } } catch (InterruptedException e) { System.out.println("锁获取被中断"); } }
|
使用场景:需要响应取消请求的长时任务。
超时获取
public boolean tryLockWithTimeout() { try { if (lock.tryLock(3, TimeUnit.SECONDS)) { try { return true; } finally { lock.unlock(); } } else { System.out.println("获取锁超时"); return false; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } }
|
使用场景:避免死锁、设置操作超时时间。
Condition 条件变量
ReentrantLock 可以创建多个 Condition,实现更精细的线程等待/通知:
public class BoundedBuffer<T> { private final ReentrantLock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private final Object[] items; private int putIndex, takeIndex, count; public void put(T x) throws InterruptedException { lock.lock(); try { while (count == items.length) { notFull.await(); } items[putIndex] = x; putIndex = (putIndex + 1) % items.length; count++; notEmpty.signal(); } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); } @SuppressWarnings("unchecked") T x = (T) items[takeIndex]; takeIndex = (takeIndex + 1) % items.length; count--; notFull.signal(); return x; } finally { lock.unlock(); } } }
|
与 Object.wait/notify 对比:
| 特性 |
Condition |
Object wait/notify |
| 数量 |
多个 |
一个 |
| 等待队列 |
精确唤醒 |
可能唤醒错误线程 |
| 响应中断 |
awaitUninterruptibly |
不支持 |
| 超时等待 |
支持纳秒级 |
毫秒级 |
锁的查询方法
ReentrantLock lock = new ReentrantLock();
Thread owner = lock.getOwner();
boolean hasWaiters = lock.hasWaiters(condition);
int waitQueueLength = lock.getWaitQueueLength(condition);
boolean heldByCurrent = lock.isHeldByCurrentThread();
boolean locked = lock.isLocked();
boolean fair = lock.isFair();
int holdCount = lock.getHoldCount();
|
读写锁 ReentrantReadWriteLock
public class Cache { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); private Map<String, Object> data = new HashMap<>(); public Object get(String key) { r.lock(); try { return data.get(key); } finally { r.unlock(); } } public void put(String key, Object value) { w.lock(); try { data.put(key, value); } finally { w.unlock(); } } }
|
特点:
StampedLock(JDK 8)
public class Point { private double x, y; private final StampedLock sl = new StampedLock(); public double distanceFromOrigin() { long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }
|
最佳实践
- 优先使用 synchronized:简单场景,JVM 优化好
- 需要高级功能时用 ReentrantLock:可中断、超时、公平锁、多条件
- 始终在 finally 中 unlock
- 读多写少用 ReadWriteLock
- 避免锁嵌套:防止死锁
lock.lock(); try { } finally { lock.unlock(); }
|
总结
ReentrantLock 提供了比 synchronized 更灵活的锁机制,但使用复杂度也更高。在大多数场景下,synchronized 已经足够;只有在需要可中断、超时、公平性或多条件等待时,才考虑使用 ReentrantLock。
核心要点
ReentrantLock 支持公平锁和非公平锁,默认是非公平锁
提供了 tryLock()、lockInterruptibly() 等高级功能
必须在 finally 块中释放锁,否则可能造成死锁
公平锁性能较低,但保证线程获取锁的顺序
总结
ReentrantLock 在需要高级功能时比 synchronized 更合适。在实际项目中,大多数场景使用 synchronized 就足够了,只有在需要定时锁、可中断锁等功能时才考虑 ReentrantLock。