ReentrantLock 介绍-Java
ReentrantLock
是 Java 并发包(java.util.concurrent.locks
)提供的一种显式锁机制。相比于 synchronized
,ReentrantLock
提供了更多的锁控制功能,例如可中断锁、定时锁、条件变量和公平锁。
1. ReentrantLock
ReentrantLock
是一种 可重入的显式锁。它的可重入性允许同一个线程多次获取同一把锁(无需陷入死锁),并通过手动调用 lock()
和 unlock()
实现锁的获取和释放。
- 显式锁:需要手动获取(
lock()
)和释放(unlock()
)。 - 可重入性:同一线程可以多次获取同一把锁,每次获取都需显式释放。
2. ReentrantLock 的基本用法
以下是 ReentrantLock
的基本用法:
- 创建锁对象:
- 获取锁:
- 释放锁:
示例代码:基本用法
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); private int counter = 0; public void increment() { lock.lock(); // 获取锁 try { counter++; } finally { lock.unlock(); // 确保释放锁 } } public int getCounter() { lock.lock(); try { return counter; } finally { lock.unlock(); } } }
3. ReentrantLock 的高级功能
ReentrantLock
提供了以下增强功能,相较于 synchronized
更加灵活。
(1) 可中断锁
线程在尝试获取锁时可以被中断。这是通过 lockInterruptibly()
方法实现的。
lock.lockInterruptibly(); // 可中断的获取锁
使用示例:
public void safeMethod() { try { lock.lockInterruptibly(); // 如果被中断,将抛出 InterruptedException // 关键逻辑 } catch (InterruptedException e) { System.out.println("Thread was interrupted while waiting for lock"); } finally { lock.unlock(); } }
(2) 锁超时
支持尝试在指定时间内获取锁,如果超时仍未获取到锁,则返回 false
。
if (lock.tryLock(2, TimeUnit.SECONDS)) { try { // 获取锁后执行逻辑 } finally { lock.unlock(); } } else { System.out.println("Failed to acquire lock within 2 seconds"); }
(3) 公平锁与非公平锁
- 非公平锁(默认):线程获取锁的顺序不一定按照请求锁的顺序,性能更高。
- 公平锁:线程获取锁时按照请求锁的先后顺序,保证公平性。
创建公平锁:
ReentrantLock lock = new ReentrantLock(true); // 公平锁
(4) 条件变量
ReentrantLock
提供了 Condition
对象,用于线程之间的协调。一个锁可以有多个条件变量,通过条件变量可以实现更灵活的线程同步。
常用方法:
await()
:使线程等待,并释放锁。signal()
:唤醒一个等待线程。signalAll()
:唤醒所有等待线程。
示例:生产者-消费者模型
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ProducerConsumer { private final ReentrantLock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); private final Condition notFull = lock.newCondition(); private final int[] buffer = new int[10]; private int count = 0, putIndex = 0, takeIndex = 0; public void produce(int value) throws InterruptedException { lock.lock(); try { while (count == buffer.length) { notFull.await(); // 等待缓冲区有空位 } buffer[putIndex] = value; putIndex = (putIndex + 1) % buffer.length; count++; notEmpty.signal(); // 唤醒消费者 } finally { lock.unlock(); } } public int consume() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); // 等待缓冲区有数据 } int value = buffer[takeIndex]; takeIndex = (takeIndex + 1) % buffer.length; count--; notFull.signal(); // 唤醒生产者 return value; } finally { lock.unlock(); } } }
4. ReentrantLock 的实现原理
(1) 基于 AQS 实现
ReentrantLock
的核心是基于 AbstractQueuedSynchronizer (AQS)
实现的,它维护了一个 FIFO 的线程等待队列。
- 独占模式:
ReentrantLock
的锁是一种独占锁,只有一个线程可以持有。 - 可重入性:
ReentrantLock
记录当前线程获取锁的次数(通过state
变量),每次释放锁时state
减少,直到为 0 时完全释放。
(2) 加锁与解锁
lock()
方法通过 CAS 操作尝试将状态从0
修改为1
。- 如果锁已经被占用,线程会加入 AQS 的等待队列。
- 释放锁时,
unlock()
会将状态从1
修改为0
,并唤醒等待队列中的线程。
5. ReentrantLock 的优缺点
优点
- 灵活性更高:支持可中断锁。支持超时获取锁。支持条件变量,灵活实现线程协调。
- 公平锁:提供公平锁选项,保证线程按请求顺序获取锁。
- 更适合高并发场景:性能更高,适用于复杂并发需求。
缺点
- 复杂性增加:必须显式调用 lock() 和 unlock(),使用不当可能导致死锁。
- 代码冗长:相较于 synchronized,代码更加冗长。
6. 使用建议
使用 ReentrantLock
的场景
- 需要使用 超时锁 或 中断锁。
- 需要实现复杂的线程同步逻辑(如条件变量)。
- 需要公平锁机制确保线程按顺序执行。
- 需要更高性能或灵活性时。
使用 synchronized
的场景
- 同步逻辑简单,代码清晰是首要需求。
- 不需要复杂的线程协调功能。
Java碎碎念 文章被收录于专栏
来一杯咖啡,聊聊Java的碎碎念呀