AQS源码解析之公平锁与非公平锁
发布时间: 2024-02-16 09:22:56 阅读量: 58 订阅数: 43
# 1. 引言
#### 1.1 介绍AQS(AbstractQueuedSynchronizer)的作用和基本原理
AQS(AbstractQueuedSynchronizer)是Java并发框架中提供的一个基础工具类,用于实现同步器的构建。它是实现锁和其他同步器(如Semaphore、CountDownLatch等)的核心组件。AQS提供了一套抽象的队列同步器接口,以及一些默认的实现,这些实现在实现自定义同步器时非常有用。
AQS的基本原理是通过一个FIFO队列来管理线程的竞争和排队。该队列中的节点(Node)代表线程,节点内部维护了相关的状态信息,如等待状态、锁的拥有者等。当线程获取锁时,如果锁是可用的,则线程可以直接获取锁;如果锁被其他线程持有,则当前线程会被包装成节点,并被放入队列尾部,进入等待状态。当锁的持有者释放锁时,AQS会通过相应的算法将等待队列中的线程唤醒并从队列中移除,使得其能够重新进行竞争。
#### 1.2 解释公平锁和非公平锁的概念和区别
公平锁和非公平锁是指在多个线程竞争同一个锁时,锁的获取顺序的不同策略。
- **公平锁**:在多个线程同时竞争一个锁时,会按照线程的请求顺序进行排队,先到先得。即先到的线程先尝试获取锁,而后到的线程会加入到等待队列,等待前面的线程释放锁后再进行竞争。公平锁保证了线程获取锁的顺序按照其到达的顺序,维护了系统的公平性。
- **非公平锁**:在多个线程竞争同一个锁时,不考虑线程的到达顺序,允许后到的线程直接获取锁,即插队。非公平锁的主要目标是为了提高系统的吞吐量和性能,允许后到的线程有机会直接获取锁,减少了等待的时间。
公平锁和非公平锁的区别在于对锁的获取顺序的处理策略,在某些场景下,公平锁会导致更高的线程切换开销,而非公平锁则会导致某些线程长时间无法获取到锁。因此,在实际应用中需要根据具体情况选择合适的锁策略。在接下来的章节中,我们将分析AQS在公平锁和非公平锁实现上的原理和区别。
# 2. AQS源码解析
在本章中,我们将深入探讨AQS(AbstractQueuedSynchronizer)的源码实现细节。首先,我们将介绍AQS的源码结构概述,然后分别对公平锁和非公平锁的实现原理进行详细解析。通过本章的学习,你将更好地理解AQS的内部机制,并能够熟练分析其源码实现。
#### 2.1 AQS源码结构概述
AQS是Java并发包中提供的一种同步器框架,其核心思想是通过一个FIFO队列来管理线程的排队和唤醒,从而实现对共享资源的访问控制。AQS主要由以下几个部分组成:
- **同步队列(Sync Queue)**:用于存放被阻塞的线程,以及管理线程获取锁的排队顺序。
- **State变量**:记录共享资源的状态,通过CAS操作进行状态变更。
- **内部同步器(Sync)**:定义了获取锁、释放锁等操作的接口,具体子类(如ReentrantLock、CountDownLatch等)实现这些操作的具体逻辑。
AQS的内部实现比较复杂,但核心思想是基于状态的抽象,具体的同步操作由继承AQS的具体子类来实现。接下来,我们将分别对公平锁和非公平锁的实现原理进行解析。
#### 2.2 公平锁实现原理解析
##### 2.2.1 公平锁的队列机制
在AQS内部,对于公平锁,同步队列中的节点按照线程请求的先后顺序排队。每个线程都会创建一个节点(Node)并加入到队列尾部进行排队等待获取锁。
具体来说,当一个线程请求获取锁时,如果发现当前有其他线程持有锁或者队列不为空,那么该线程会以排队的方式加入等待队列。同时,AQS会确保队列中的节点按照先后顺序依次获取锁,即先进入队列的线程先获得锁的访问权限。
##### 2.2.2 公平锁的获取与释放流程
对于公平锁,线程获取锁的过程通常包括以下几个步骤:
- 线程通过acquire方法尝试获取锁,如果获取失败就会加入到等待队列中,并进入自旋状态。
- 当锁释放时,AQS会按照队列中的顺序唤醒等待线程,使得等待队列中的线程按照FIFO顺序逐个获取锁。
上述是公平锁的主要实现原理,接下来我们将对非公平锁的实现原理进行类似的解析。
# 3. 公平锁与非公平锁对比分析
在理解了AQS源码的基础上,我们来对比分析公平锁和非公平锁的特点和适用场景。
#### 3.1 基本性能特征比较
- 公平锁:
- 优点:
- 公平性较强:保证等待时间较长的线程能够优先获取锁。
- 避免饥饿:所有线程都有获取锁的机会。
- 缺点:
- 竞争激烈时性能下降:线程需要竞争锁,可能导致额外的线程调度和上下文切换。
- 可能存在线程饥饿问题:某些线程可能一直无法获取到锁。
- 非公平锁:
- 优点:
- 性能较高:线程可以直接尝试获取锁,减少了线程上下文切换的开销。
- 可能减少线程饥饿:如果锁一直可用,就没有线程饥饿问题。
- 缺点:
- 不保证公平性:可能会导致某些线程长期无法获取到锁。
#### 3.2 对系统公平性的影响
- 公平锁:确保了线程的公平性,避免了某些线程长时间无法获取锁的问题,但在高并发情况下性能可能下降。
- 非公平锁:优先为当前线程分配锁资源,性能较高,但可能导致某些线程饥饿,长期无法获取到锁资源。
#### 3.3 在不同场景的适用性分析
- 公平锁适用的场景:
- 对锁获取的顺序有严格要求,需要保证线程等待锁的时间能够得到合理的安排。
- 系统中的并发较低,竞争较少,公平性对性能影响不大。
- 非公平锁适用的场景:
- 对性能要求较高,竞争较激烈的场景。
- 可以容忍某些线程长期无法获取到锁的情况,或者系统中锁的竞争较少的情况。
通过对比分析,我们可以根据具体的业务场景选择合适的锁类型,来平衡性能和公平性的需求。
接下来,我们将以实际的代码示例来分析公平锁的应用和使用。
至于第四章节和第五章节的内容,请耐心等待,我们稍后完成。
# 4. 公平锁
在本章中,我们将以`ReentrantLock`为例对AQS的源码进行解析,并讨论其在实际场景中的应用。
### 4.1 以ReentrantLock为例进行源码解析
`ReentrantLock`是Java中常用的可重入锁实现,它内部通过AQS来实现锁的获取和释放。我们将重点分析`ReentrantLock`中公平锁的实现原理。
首先,让我们来看一下`ReentrantLock`的构造方法和常用方法的定义:
```java
public class ReentrantLock implements Lock, java.io.Serializable {
// ...
private static class Sync extends AbstractQueuedSynchronizer {
// ...
Sync() {
// 公平锁通过传入true来创建
super(true);
}
// ...
}
// ...
public ReentrantLock() {
sync = new NonfairSync();
}
// ...
}
```
`ReentrantLock`中定义了一个内部的静态类`Sync`,它是通过继承`AbstractQueuedSynchronizer`来实现的。`Sync`中的构造方法指定了使用公平锁。
### 4.2 公平锁的实例应用场景及示例代码
公平锁适用于需要保证多个线程获取锁的顺序的场景,确保先请求锁的线程能够先获取到锁。下面是一个使用公平锁的示例,模拟了一个售票系统:
```java
import java.util.concurrent.locks.ReentrantLock;
public class TicketSystem {
private static int tickets = 100;
private static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
while (tickets > 0) {
try {
lock.lock();
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "张票");
tickets--;
}
} finally {
lock.unlock();
}
}
}).start();
}
}
}
```
在上述示例中,我们使用了一个静态的`ReentrantLock`对象来保护共享的`tickets`变量,同时传入`true`来创建公平锁。每个线程在卖票时,先获取锁,然后检查是否还有余票,如果有则售卖一张,并将余票数量减一。最后,无论是否售卖成功,都需要释放锁,以便其他线程可以继续执行。
以上就是以`ReentrantLock`为例进行源码解析,并展示了公平锁在售票系统中的使用情景及相应的示例代码。
在接下来的章节中,我们将继续讨论AQS的源码并分析非公平锁的实现原理。
# 5. 非公平锁
在本节中,我们将以`ReentrantLock`为例,深入分析AQS中非公平锁的源代码实现细节。我们将首先对AQS中非公平锁的实现原理进行解析,然后通过示例代码和应用场景来说明非公平锁的具体应用。
#### 5.1 `ReentrantLock`源码解析
Java中的`ReentrantLock`是使用AQS实现的一种可重入锁,通过查看`ReentrantLock`的源码,我们可以深入了解非公平锁的实现细节。我们将重点关注`ReentrantLock`中涉及到AQS的非公平锁获取与释放的核心代码部分。
```java
// 示例代码以Java语言为例,实现非公平锁的源码解析
public class ReentrantLock implements Lock, java.io.Serializable {
// AQS的内部类Sync是实现非公平锁的关键
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁获取的实现,调用AQS的acquire方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// 非公平锁释放的实现,调用AQS的release方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
}
```
#### 5.2 非公平锁的实例应用场景及示例代码
非公平锁适合于某些特定场景,比如线程能够通过其他途径获得资源,而不一定需要排队。下面我们通过一个简单的示例场景来说明非公平锁的应用。
```java
// 示例代码以Java语言为例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class NonFairLockExample {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 创建多个线程竞争非公平锁
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Task());
thread.start();
}
}
static class Task implements Runnable {
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired the lock");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " released the lock");
}
}
}
}
```
在上述示例中,我们创建了5个线程来竞争一个非公平锁,由于非公平锁并不保证获取锁的公平性,因此多个线程可能会出现抢占锁的情况。通过运行该示例,我们可以观察到多个线程获取和释放非公平锁的情况,从而理解非公平锁在实际应用中的特点。
通过本节的分析,我们深入了解了AQS中非公平锁的源码实现细节,并通过示例场景展示了非公平锁的具体应用。
# 6. 结论
### 6.1 总结公平锁与非公平锁的特点和使用场景
在本文中, 我们对AQS的实现原理进行了详细的解析,包括公平锁和非公平锁的实现机制。接下来,我们对公平锁与非公平锁进行一个总结,并分析它们在不同场景下的适用性。
公平锁强调的是线程获取锁的顺序与其请求锁的顺序相同,即按照先来先服务的原则分配锁。因此公平锁能够保证线程获取锁的公平性,在某些场景下非常有用。例如在任务调度的场景中,如果有多个线程在等待一个锁,公平锁能够保证先申请锁的线程先获得锁,从而实现任务的有序执行。然而,公平锁由于需要维护一个有序的等待队列,因此在高并发的情况下性能可能较低。
非公平锁则没有严格保证线程获取锁的顺序,它允许已经持有锁的线程再次获取到锁,也允许未持有锁的线程插队获取锁。这种方式能够减少锁的竞争,提高系统的整体性能。因此,在某些高并发且对锁获取顺序没有严格要求的场景下,非公平锁可能更加合适。例如在一些读多写少的情况下,非公平锁能够通过快速获取策略提升系统的性能。
### 6.2 对AQS源码解析的回顾和展望
通过对AQS源码的深入剖析,我们对其在公平锁和非公平锁实现中的关键原理有了更深入的理解。我们了解了AQS的核心数据结构及其在锁的获取与释放过程中的具体应用。
然而,AQS源码仍然非常庞大且复杂,我们仅仅对其中公平锁和非公平锁的实现原理进行了分析,仍有许多细节值得深入探究。同时,我们还可以进一步研究AQS在并发编程中其他重要概念的应用,如条件变量、阻塞队列等。
对于开发者而言,深入理解AQS的工作原理对于编写高效且线程安全的并发代码非常重要。我们希望本文能够为读者提供一个基础的理论基础,并鼓励大家进一步深入学习和研究AQS的源码,以便在实践中能够更好地应用AQS提供的强大功能。
通过对AQS源码解析的持续学习和实践,我们相信可以更好地掌握并发编程,提高系统性能,实现高效可靠的多线程应用。
0
0