ReentrantLock源码解析
发布时间: 2024-01-10 14:05:55 阅读量: 44 订阅数: 34
ReentrantLock源码解析(二)
# 1. 【ReentrantLock源码解析】
## 1. 简介
### 1.1 什么是ReentrantLock
ReentrantLock是Java提供的一种可重入的互斥锁,它可以用于替代synchronized关键字。相较于synchronized,ReentrantLock提供了更多的灵活性和功能。
### 1.2 ReentrantLock的作用和特点
ReentrantLock用于保护临界区,防止多个线程同时访问共享资源造成数据不一致或错误的情况。它具有以下特点:
- 可重入:同一个线程可以多次获得同一个锁,避免死锁的发生。
- 公平性:可以选择公平性或非公平性的锁获取方式。
- 等待可中断:提供了可中断响应的锁等待方式。
- 条件变量:提供了Condition接口来实现线程间的等待和唤醒机制。
### 1.3 为什么使用ReentrantLock
ReentrantLock相对于synchronized关键字有以下优点:
- 可中断:当线程在等待锁时,可以根据需要中断等待,而synchronized关键字不具备这个功能。
- 公平性:可以选择公平性或非公平性的锁获取方式,而synchronized关键字只能是非公平的。
- 等待超时:可以在获取锁时设置等待超时时间,而synchronized关键字没有提供这个功能。
- 条件变量:提供了Condition接口来实现线程间的等待和唤醒机制,可以更灵活地控制线程的执行顺序。
## 2. 基本用法
ReentrantLock的基本用法主要包括创建ReentrantLock对象、获取锁、释放锁、条件变量等操作。
### 2.1 创建ReentrantLock对象
使用ReentrantLock的第一步是创建ReentrantLock对象,可以选择公平性或非公平性的锁获取方式。
```java
ReentrantLock lock = new ReentrantLock(); // 创建非公平锁
ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
```
### 2.2 lock方法和unlock方法
获取锁和释放锁是ReentrantLock的核心操作,可以使用`lock`方法获取锁,使用`unlock`方法释放锁。
```java
lock.lock(); // 获取锁
try {
// 临界区代码
} finally {
lock.unlock(); // 释放锁
}
```
### 2.3 tryLock方法和tryLock带时间限制的方法
使用`tryLock`方法尝试获取锁,如果获取成功返回true,否则返回false,不会阻塞线程。
```java
if (lock.tryLock()) {
try {
// 临界区代码
} finally {
lock.unlock(); // 释放锁
}
} else {
// 无法获取锁的处理
}
```
还可以使用`tryLock`方法的带时间限制的重载版本,在指定的时间范围内尝试获取锁。
```java
if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
try {
// 临界区代码
} finally {
lock.unlock(); // 释放锁
}
} else {
// 在指定时间内无法获取锁的处理
}
```
### 2.4 锁的重入性
ReentrantLock允许同一个线程多次获取同一个锁,这种特性称为锁的重入性。可以避免死锁的发生,提高代码灵活性。
```java
lock.lock(); // 第一次获取锁
try {
// 临界区代码
lock.lock(); // 第二次获取锁
try {
// 临界区代码
} finally {
lock.unlock(); // 第二次释放锁
}
} finally {
lock.unlock(); // 第一次释放锁
}
```
这样,同一个线程内部就可以多次获取和释放锁,保证了线程对共享资源的正确访问。
继续阅读下一章节:【3. 公平性与非公平性】
# 2. 基本用法
ReentrantLock提供了一些基本的方法,用于加锁和释放锁。
### 2.1 创建ReentrantLock对象
要使用ReentrantLock,首先需要创建一个ReentrantLock对象。可以使用无参构造函数来创建一个非公平锁(默认),或者使用带有布尔值参数的构造函数来创建一个公平锁。
```java
// 创建一个非公平锁
ReentrantLock lock = new ReentrantLock();
// 创建一个公平锁
ReentrantLock fairLock = new ReentrantLock(true);
```
### 2.2 lock方法和unlock方法
`lock`方法用于获取锁,若锁已被其他线程占用,则当前线程会被阻塞直至获取到锁为止。
`unlock`方法用于释放锁,需要确保在每次获取到锁后,都会对应地释放锁,以免造成资源泄露。
下面是一个简单的示例代码:
```java
class MyTask implements Runnable {
private ReentrantLock lock;
public MyTask(ReentrantLock lock) {
this.lock = lock;
}
@Override
public void run() {
lock.lock(); // 获取锁
try {
// 执行需要加锁保护的代码
// ...
} finally {
lock.unlock(); // 释放锁
}
}
}
public class Main {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
MyTask task = new MyTask(lock);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
```
### 2.3 tryLock方法和tryLock带时间限制的方法
`tryLock`方法尝试获取锁,如果锁可用,则获取到锁并立即返回`true`,如果锁不可用,则立即返回`false`,而不会阻塞当前线程。
`tryLock(long timeout, TimeUnit unit)`方法与`tryLock`方法类似,但是它会在一段时间内尝试获取锁,如果超过指定时间仍未获取到锁,则返回`false`。
下面的示例代码演示了如何使用`tryLock`方法:
```java
class MyTask implements Runnable {
private ReentrantLock lock;
public MyTask(ReentrantLock lock) {
this.lock = lock;
}
@Override
public void run() {
if (lock.tryLock()) { // 尝试获取锁
try {
// 执行需要加锁保护的代码
// ...
} finally {
lock.unlock(); // 释放锁
}
} else {
// 获取锁失败,进行其他处理
// ...
}
}
}
public class Main {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
MyTask task = new MyTask(lock);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
```
### 2.4 锁的重入性
ReentrantLock是可重入锁,支持多次加锁和解锁。在同一个线程中,可以多次调用`lock`方法,而不会出现死锁。
```java
class MyTask implements Runnable {
private ReentrantLock lock;
public MyTask(ReentrantLock lock) {
this.lock = lock;
}
@Override
public void run() {
lock.lock(); // 第一次获取锁
try {
System.out.println("Thread " + Thread.currentThread().getId() + " acquired the lock for the first time.");
lock.lock(); // 第二次获取锁
try {
System.out.println("Thread " + Thread.currentThread().getId() + " acquired the lock again.");
// 执行需要加锁保护的代码
// ...
} finally {
lock.unlock(); // 第二次释放锁
System.out.println("Thread " + Thread.currentThread().getId() + " released the lock again.");
}
} finally {
lock.unlock(); // 第一次释放锁
System.out.println("Thread " + Thread.currentThread().getId() + " released the lock for the first time.");
}
}
}
public class Main {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
MyTask task = new MyTask(lock);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
```
在上面的示例代码中,我们创建了两个线程,它们共享了同一个ReentrantLock对象。当一个线程获取到锁后,可以再次获取锁而不会被阻塞,直到释放相同次数的锁。输出结果显示了锁的重入性。
通过上述代码示例,我们对ReentrantLock的基本用法进行了介绍和演示。接下来,我们将深入探讨ReentrantLock的公平性和非公平性。
# 3. 公平性与非公平性
#### 3.1 什么是公平性和非公平性
在多线程编程中,公平性和非公平性是指线程获取锁的顺序是否按照其请求的顺序进行分配。如果锁是公平的,那么等待时间最长的线程将会获得锁,即按照FIFO(先进先出)的顺序分配锁。而非公平的锁则不保证按照请求的顺序进行分配,有可能会出现插队现象。
#### 3.2 ReentrantLock的公平性和非公平性
在ReentrantLock中,可以通过构造函数来指定锁的公平性,例如:
```java
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
```
#### 3.3 公平与非公平的区别和应用场景
公平性会增加线程调度的开销,因为线程需要按照顺序排队获取锁,而非公平性可能会导致某些线程长时间无法获取到锁。在实际应用中,如果对线程获取锁的顺序有严格要求,可以选择公平锁;如果对性能要求较高,可以选择非公平锁。例如,对于某些需要保证交易顺序的场景,公平锁会更适合;而对于某些高并发场景,非公平锁可能更适合。
以上是ReentrantLock的公平性与非公平性的基本概念和使用场景,接下来我们将对Condition条件变量进行详细介绍。
# 4. Condition条件变量
#### 4.1 什么是Condition
Condition是用来对线程进行等待和唤醒操作的工具类,它和传统的synchronized关键字中的wait()、notify()、notifyAll()方法相对应。Condition是可与Lock关联的,一个Lock对象可以有多个Condition对象,线程可以通过Condition对象来实现等待和唤醒机制。
#### 4.2 ReentrantLock中的Condition的基本使用
在ReentrantLock中,我们可以通过newCondition()方法创建一个Condition对象,并使用await()方法进行等待,使用signal()或signalAll()方法进行唤醒。
下面是一个使用Condition的简单示例:
```java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean flag = false;
public void waitForFlag() throws InterruptedException {
lock.lock();
try {
while (!flag) {
condition.await();
}
System.out.println("Flag is now true");
} finally {
lock.unlock();
}
}
public void setFlag() {
lock.lock();
try {
flag = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionExample example = new ConditionExample();
Thread thread1 = new Thread(() -> {
try {
example.waitForFlag();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> example.setFlag());
thread1.start();
Thread.sleep(1000); // 等待1秒
thread2.start();
thread1.join();
thread2.join();
}
}
```
在上面的例子中,我们创建了一个Condition对象condition,并在waitForFlag()方法中使用condition.await()进行等待,直到flag为true时,才会继续向下执行。在setFlag()方法中,我们将flag设置为true,并使用condition.signalAll()唤醒正在等待的线程。
#### 4.3 Condition的生产者-消费者模式示例
使用Condition可以很方便地实现经典的生产者-消费者模式。下面是一个使用Condition实现生产者-消费者模式的示例代码:
```java
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerExample {
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
private Queue<Integer> queue = new LinkedList<>();
private int maxSize = 5;
public void produce() throws InterruptedException {
lock.lock();
try {
while (queue.size() == maxSize) {
notFull.await();
}
int num = (int) (Math.random() * 10);
queue.add(num);
System.out.println("Produced: " + num);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int num = queue.poll();
System.out.println("Consumed: " + num);
notFull.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ProducerConsumerExample example = new ProducerConsumerExample();
Thread producerThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
example.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumerThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
example.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producerThread.start();
consumerThread.start();
}
}
```
在上面的例子中,我们使用一个队列queue作为缓冲区,生产者线程通过produce()方法将随机生成的数字加入到队列中,消费者线程通过consume()方法从队列中取出数字。当队列已满时,生产者线程进入等待状态,直到队列不满时被唤醒;当队列为空时,消费者线程进入等待状态,直到队列不空时被唤醒。
通过这个示例可以看到,使用Condition可以很方便地实现线程间的等待和唤醒机制,从而实现复杂的线程协作。
# 5. 锁的可重入性与条件变量
在并发编程中,锁的可重入性是一个重要的特性,它指的是同一个线程可以重复获取已经持有的锁。ReentrantLock作为一种可重入锁,能够帮助我们实现锁的可重入性。同时,ReentrantLock和Condition条件变量的配合使用,可以更灵活地实现线程间的通信和协作。
### 5.1 锁的可重入性说明
可重入性是指同一个线程可以重复获取已经持有的锁,即线程在持有锁的时候,再次获取同一个锁是允许的。这种机制可以避免线程因为自身逻辑需要而阻塞,保证线程的执行流程顺畅进行。
下面是一个示例,演示了锁的可重入性:
```python
import threading
class ReentrantLockDemo:
lock = threading.Lock()
def method1(self):
with self.lock:
print("进入method1方法")
self.method2()
def method2(self):
with self.lock:
print("进入method2方法")
demo = ReentrantLockDemo()
thread1 = threading.Thread(target=demo.method1)
thread1.start()
```
上述代码中,`ReentrantLockDemo`类中的`method1`和`method2`方法都使用了同一个锁对象进行了上锁操作。当线程执行`method1`方法时,它会获取锁并进入临界区,然后调用`method2`方法。由于这两个方法使用的是同一个锁对象,所以在`method2`方法中再次调用`with self.lock`时,由于是同一线程持有的锁,所以可以继续获取锁进入临界区。
### 5.2 Condition在可重入锁中的应用
在ReentrantLock中,Condition是与Lock配套使用的,可以更灵活地实现线程间的通信和协作。
下面以生产者-消费者模式示例,演示了Condition的基本使用:
```java
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;
public void produce(int num) {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.add(num);
System.out.println("生产者生产:" + num);
notEmpty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int num = queue.poll();
System.out.println("消费者消费:" + num);
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo demo = new ConditionDemo();
Thread producerThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
demo.produce(i);
}
});
Thread consumerThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
demo.consume();
}
});
producerThread.start();
consumerThread.start();
}
}
```
上述代码中,我们使用ReentrantLock创建了一个互斥锁,并使用Condition创建了两个条件变量`notFull`和`notEmpty`。生产者在队列容量已满时,调用`notFull.await()`进行等待,消费者在队列为空时,调用`notEmpty.await()`进行等待。当有生产或消费操作完成后,需要通知等待的线程时,分别调用`notFull.signalAll()`和`notEmpty.signalAll()`进行通知。
### 5.3 锁的可重入性和Condition的配合使用
锁的可重入性和Condition的配合使用可以帮助我们更好地管理和协调线程间的执行顺序。通过使用可重入锁和Condition的`await`、`signal`、`signalAll`方法,我们可以编写出更灵活和高效的并发程序。
在ReentrantLock中,可重入锁和Condition的配合使用示例如下:
```python
import threading
class ReentrantLockWithConditionDemo:
lock = threading.Lock()
condition = threading.Condition(lock)
count = 0
def increment(self):
with self.lock:
self.count += 1
self.condition.notifyAll()
def decrement(self):
with self.lock:
while self.count == 0:
self.condition.wait()
self.count -= 1
demo = ReentrantLockWithConditionDemo()
thread1 = threading.Thread(target=demo.decrement)
thread2 = threading.Thread(target=demo.increment)
thread1.start()
thread2.start()
```
上述代码中,`ReentrantLockWithConditionDemo`类中的`increment`方法和`decrement`方法分别用来增加和减少一个计数器。在`decrement`方法中,我们通过`condition.wait()`进行等待,当计数器为零时,线程会进入等待状态。在`increment`方法中,我们通过`condition.notifyAll()`进行通知,当计数器被修改为非零时,等待的线程会被唤醒。
通过锁的可重入性和Condition的配合使用,我们可以实现更复杂的线程间通信和协作,编写出更安全和高效的并发程序。
# 6. 源码剖析
在本章中,我们将深入研究ReentrantLock的源代码,并了解它的类结构和关键方法,以及它的实现原理和一些优化技巧。
### 6.1 ReentrantLock的类结构和关键方法
ReentrantLock是一个可重入的互斥锁,它实现了`Lock`接口,并在内部使用了`AbstractQueuedSynchronizer (AQS)`同步器来实现锁的功能。
ReentrantLock的类结构大致如下:
```
public class ReentrantLock implements Lock,Serializable {
// ...
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
}
// ...
}
```
- `ReentrantLock`实现了`Lock`接口,它包含了一些基本的获取锁和释放锁的方法,例如`lock()`和`unlock()`等。
- `ReentrantLock`内部定义了两个静态内部类`NonfairSync`和`FairSync`,它们分别实现了公平锁和非公平锁的逻辑。
- `Sync`类是`ReentrantLock`的抽象内部类,它继承了`AbstractQueuedSynchronizer`类,并实现了锁的核心功能。
重要的方法包括:
- `lock()`:获取锁,如果锁被占用,则进入等待队列中等待其他线程释放锁。
- `unlock()`:释放锁,唤醒等待队列中的一个线程获取锁。
- `tryAcquire(int acquires)`:尝试获取锁,根据实际情况调用`nonfairTryAcquire()`或`fairTryAcquire()`方法来获取锁。
- `tryRelease(int releases)`:尝试释放锁,根据实际情况调用`nonfairTryRelease()`或`fairTryRelease()`方法来释放锁。
### 6.2 独占锁和共享锁的实现原理
ReentrantLock支持两种锁:独占锁和共享锁。其中,独占锁允许只有一个线程获取锁,并可以重入;共享锁允许多个线程同时获取锁。
ReentrantLock使用AQS同步器实现这两种锁的功能。AQS通过维护一个FIFO的等待队列来管理等待锁的线程,并使用CAS原子操作来保证操作的原子性。当一个线程调用`lock()`方法时,它会尝试获取锁,如果锁已经被占用,它会进入等待队列中。当锁被释放时,AQS会从等待队列中唤醒一个线程,使其获取锁。
在独占锁的情况下,AQS会维护一个状态变量来标识锁的状态。当一个线程成功获取锁时,AQS会将状态值加1,当线程释放锁时,状态值减1。只有当状态值为0时,其他线程才能获取该锁。
在共享锁的情况下,AQS会维护一个状态变量来表示共享锁的数量。当一个线程成功获取共享锁时,AQS会将状态值加1,当线程释放锁时,状态值减1。只有当状态值为0时,其他线程才能获取该锁。
### 6.3 AQS同步器的使用和实现
AQS(AbstractQueuedSynchronizer)是ReentrantLock内部使用的同步器,它提供了一些抽象方法和队列操作方法,用于实现锁的获取和释放等功能。
在AQS中,有两个重要的队列:`exclusiveQueue`(等待独占锁的队列)和`sharedQueue`(等待共享锁的队列)。当一个线程获取锁失败时,它会被加入到相应的等待队列中,当锁被释放时,AQS会从队列中取出一个线程并唤醒它。
AQS提供了以下几个核心方法:
- `acquire(int arg)`:获取锁,如果获取失败将线程加入等待队列中。
- `release(int arg)`:释放锁,将锁的状态恢复为非占用状态,并唤醒等待队列中的一个线程。
- `tryAcquireShared(int arg)`:尝试获取共享锁,成功返回true,失败返回false。
- `tryReleaseShared(int arg)`:尝试释放共享锁。
具体的实现逻辑和方法调用流程可以参考AQS源码进行分析。
### 6.4 ReentrantLock的高级特性和优化技巧
除了基本的获取锁和释放锁的功能外,ReentrantLock还提供了一些高级特性和优化技巧:
- 公平锁和非公平锁的支持:可以根据实际需求选择使用公平锁还是非公平锁。
- Condition条件变量的支持:可以通过Condition实现更加灵活的线程通信和同步。
- 可重入性:ReentrantLock支持锁的重入,同一个线程可以多次获取同一个锁,不会导致死锁。
- 中断响应:可以使用`lockInterruptibly()`方法在等待锁的过程中被中断,避免线程长时间等待。
- 锁的精细控制:ReentrantLock提供了一些方法,如`getHoldCount()`和`getQueueLength()`等,用于获取持有锁的线程数量和等待锁的线程数量。
- 性能优化:ReentrantLock通过高度优化的自旋锁和CAS操作来提高锁的性能,尤其在无竞争的情况下,可以减少线程的上下文切换和内核态与用户态之间的切换。
0
0