目录
-
- 一、ReentrantLock 简介
-
- 1.1 Reentrant 的特性:
- 1.2 基本语法
- 1.3 ReentrantLock 的主要方法:
- 1.4 lock()、tryLock()、lockInterruptibly() 的区别:
- 二、Condition 条件变量
-
- 2.1 什么是 Condition 条件变量?
- 2.2 Condition 的核心方法:
- 2.3 Condition 使用示例1:等待与唤醒
- 2.4 Condition 使用示例2:生产者-消费者模式
- 三、ReentrantLock 的工作原理
- 四、synchronized 和 Lock 的区别:
- 五、使用 ReentrantLock 的注意事项
一、ReentrantLock 简介
ReentrantLock
,也被称为 “可重入锁”,是一个同步工具类,在 java.util.concurrent.locks 包下。这种锁的一个重要特点是,它允许一个线程多次获取同一个锁而不会产生死锁。这与synchronized
关键字提供的锁定机制非常相似,但ReentrantLock
提供了更高的扩展性。
1.1 Reentrant 的特性:
- 可中断:
lockInterruptibly()
方法获取的锁可以被中断。这提供了另一个相对于synchronized
关键字的优势,因为 synchronized 不支持响应中断。 - 可以设置超时时间:
tryLock(long timeout, TimeUnit unit)
方法提供了获取锁超时的功能。它的语义是 在指定的时间内如果获取到锁就返回 true,获取不到则返回 false。这种机制避免了线程无期限地等待锁释放。 - 可以设置公平锁: ReentrantLock 在初始化时提供了一个公平锁选项。公平锁会按照线程请求锁的顺序来分配锁,而不是像非公平锁那样允许线程抢占已经等待的线程的锁。公平锁可以减少 “饥饿” 的情况,但也可能降低一些性能。
- 支持多个条件变量: ReentrantLock 类中还包含一个
Condition
接口的实现,该接口允许线程在某些条件下等待(await()
方法)或唤醒(signal()
、signalAll()
方法)。这提供了一种比使用 wait() 和 notify() 更灵活和更安全的线程通信方式。 - 支持重入: ReentrantLock 的一个主要特点是它的名字所表达的含义——“可重入”。简单来说,如果一个线程已经持有了某个锁,那么它可以再次调用
lock()
方法而不会被阻塞。 这在某些需要递归锁定的场景中非常有用。锁的持有计数(state变量)会在每次成功调用lock()
方法时递归,并在每次unlock()
方法被调用时递减。
1.2 基本语法
ReentrantLock 的基本语法如下:
// 创建锁对象
private ReentrantLock lock = new ReentrantLock();
try {
// 获取锁
lock.lock();
} finally {
// 释放锁
lock.unlock();
}
1.3 ReentrantLock 的主要方法:
void lock()
:执行此方法时,如果锁处于空闲状态,当前线程将获取到锁。相反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁。boolean tryLock()
:如果锁可用,则获取锁,并立即返回 true,否则返回 false。该方法和 lock() 的区别在于,tryLock() 只是 “试图” 获取锁,如果锁不可用,不会导致当前线程被禁用,当前线程仍然继续往下执行代码。而 lock() 方法则是一定要获取到锁,如果锁不可用,就一直等待,在未获得锁之前,当前线程并不继续向下执行。boolean tryLock(long time, TimeUnit unit)
:如果锁在给定时间内没有被另一个线程保持,则获取该锁。void unlock()
:执行此方法时,当前线程将释放持有的锁。锁只能由持有者释放,如果线程并不持有锁,却执行该方法,可能导致异常的发生。Condition newCondition()
:条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的 await() 方法,而调用后,当前线程将释放锁。getHoldCount()
:查询当前线程保持此锁的次数,也就是执行此线程执行 lock 方法的次数。getQueueLength()
:返回正等待获取此锁的线程估计数,比如启动 10 个线程,1个线程获得锁,此时返回的是9。getWaitQueueLength(Condition condition)
:返回等待与此锁相关的给定条件的线程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了 condition 对象的 await 方法,那么此时执行此方法返回 10。hasWaiters(Condition condition)
:查询是否有线程等待此锁有关的给定条件(condition)。即对于指定 condition 对象,查询有多少线程执行了 condition.await() 方法。hasQueueThread(Thread thread)
:查询给定线程是否等待获取此锁。hasQueueThreads()
:是否有线程等待此锁。isFair()
:该锁是否为公平锁。isHeldByCurrentThread()
:当前线程是否保持锁锁定,线程的执行 lock() 方法的前后分别是 false 和 true。isLock()
:此锁是否有任意线程占用。lockInterruptibly()
:如果当前线程未被中断,获取锁。
1.4 lock()、tryLock()、lockInterruptibly() 的区别:
-
lock()
能获得锁就返回 true,不能的话会一直等待获取锁。 -
tryLock()
能获得所就返回 true,不能立即返回 false,不会一直等待。tryLock(long timeout, TimeUnit unit)
可以增加时间限制,如果超过该时间段还没获得锁,返回 false。 -
lock()
和lockInterruptibly()
,如果两个线程分别执行这两个方法,但如果此时中断这两个线程,lock() 不会抛出异常,而 lockInterruptibly() 会抛出异常。
二、Condition 条件变量
2.1 什么是 Condition 条件变量?
Condition
条件变量是 ReentrantLock 的一部分,它提供了比 Object 监视器方法(如wait()
和notify()
)更高级的线程等待/唤醒机制。Condition 允许更精确地控制线程间的同步,特别是当需要基于特定条件进行等待时。
Condition 接口通常与 ReentrantLock 一起使用,以实现更复杂的线程间协作模式,如:生产者-消费者模式、读者-写者模式等。
2.2 Condition 的核心方法:
下面介绍 3 个 Condition 的方法:
-
await()
:方法会使当前线程进入等待状态,并释放 ReentrantLock 锁,直到其他线程调用了相应的signal()
或signalAll()
方法时才会被唤醒。 -
signal()
:方法用于唤醒一个等待在该 Condition 上的线程。(注意:signal() 不会立即唤醒线程,而是将其放入就绪队列,由操作系统调度器决定何时真正唤醒。)
-
signalAll()
:方法会唤醒所有等待在该 Condition 上的线程。通常在需要所有等待线程都参与接下来的执行流程时使用。
2.3 Condition 使用示例1:等待与唤醒
下面这个示例展示了如何使用ReentLock和Condition实现线程间通信,通过控制锁的获取和释放以及条件变量的等待和唤醒,可以实现更灵活的线程同步和通信:
ReentrantLockExample.java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
/**
* 初始化锁
*/
private Lock lock = new ReentrantLock();
// Lock lock = new ReentrantLock(true); // 公平锁
// Lock lock = new ReentrantLock(false); // 非公平锁
/**
* 创建 Condition
*/
private Condition condition = lock.newCondition(); // 创建 Condition
/**
* wait 方法等待。
*/
private void testConditionWait