大家好,我是小马哥。最近并发编程是大厂面试中重点考察的问题。今天我们来探讨一下AQS相关的面试技巧。毕竟此类问题回答的好坏会在很大程度上影响我们面试的得分。
1. AQS在java中的使用
给代码加锁,是java中处理并发问题的重要手段。
java中的很多锁都是基于抽象类AQS(AbstractQueuedSynchronizer)实现的。
如下表所示:

2. AQS的实现机制
AQS提供了实现锁的机制,即CLH队列,同时通过ConditionObject实现了条件等待链表。
其中CLH队列如下:

-
AQS使用
private volatile int state;表示同步状态 -
通过内置的FIFO队列完成线程的排队
-
使用CAS对state进行修改。
3. AQS主要方法
AQS对外提供的用于实现锁的方法如下:

基于AQS可以实现公平锁和非公平锁,互斥锁和共享锁。
4. ReentrantReadWriteLock
ReentrantReadWriteLock中的写锁实现了互斥锁,读锁实现了共享锁。
其中写锁的互斥性,用于保证不会并发执行写操作,保证了数据的一致性。
其中读锁的共享性,确保可以多线程同时读取,提升了读的优先级,对于读操作执行逻辑较重的场合,比较合适。
在抢锁的时候,只有一个写线程可以抢锁成功,但同时可能有多个读线程抢锁成功,由于读写互斥,容易引起读线程“饿死”的情况。
4.1. 互斥锁
写线程通过调用WriteLock的lock方法来获取互斥锁。
java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock#lock
public void lock() {
sync.acquire(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
该方法首先尝试获取锁,如果获取失败,就将当前线程封装为Node对象,追加到CLH队列中,休眠,等待抢锁。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
之所以称为互斥锁,是因为该方法向CLH队列添加的节点是单个节点,只封装了一个线程。
关于互斥锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。
4.2. 共享锁
读操作通过调用读写锁ReadLock的lock来获取共享锁。
java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock#lock
public void lock() {
sync.acquireShared(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireShared
该方法首先尝试获取共享锁,如果抢锁失败,则执行线程节点入CLH队列的操作。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
之所以称为共享锁,是因为在该方法中,会判断CLH队列尾节点是互斥操作还是共享操作,如果是互斥操作,则当前线程节点追加到尾节点后面;如果是共享操作,则通过引用,挂到尾节点的横向链表上,当获取锁的时候,横向链表中的节点线程一并获取到锁。
关于共享锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。
5. ReentrantLock
ReentrantLock实现了公平锁和非公平锁。
5.1. 抢锁操作
java.util.concurrent.locks.ReentrantLock#lock
该方法用于获取互斥锁。
public void lock() {
sync.acquire(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
上述方法调用了AQS的acquire方法,用于获取锁,该方法首先尝试获取锁,如果获取锁失败,就将当前线程封装为Node接待,追加到CLH队列中。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上述方法中的tryAcquire方法,该方法调用了ReentrantLock内部类Sync的实现。
Sync类有两个子类,分别是FairSync和NofairSync。其中FairSync实现了公平锁功能,NonfairSync实现了非公平锁的功能。
5.2. 公平锁
当前线程在抢锁的时候,先判断CLH队列中有没有其他线程在等待获取锁,如果没有,再去抢锁。如果有,则乖乖排队:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 如果没有前置节点等待抢锁,才去抢
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ......
关于公平锁的获取流程,感兴趣的小伙伴可以加微信 Geanmingti 索取。
5.3. 非公平锁
tryAcquire方法的实现如下,该方法调用了nonfairTryAcquire方法用于实现非公平抢锁。
java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
// 非公平尝试获取锁
return nonfairTryAcquire(acquires);
}
下面方法中,表示没有线程持有锁,不管是否有线程在等待,直接抢,非公平。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ......
关于非公平锁的获取流程,感兴趣的小伙伴可以加微信 yuer900411_sky 索取。

关于如何学习Java,一方面需要不断的去学习,把基础知识学扎实,另一方面也要认识到java的学习不能仅仅靠理论,更多的是靠实操,所以要多练习多做项目,在实践中学习才是最好的学习方法。很多人刚开始不知道怎么去学习,那么我这里有一套Java2021最新1000道面试pdf总结,不怕面试被问到,私我获取哦

本文介绍了Java并发编程中的核心组件AQS,详细讲解了AQS的使用、实现机制,以及如何基于AQS实现ReentrantReadWriteLock的互斥锁和共享锁。还探讨了ReentrantLock的公平锁与非公平锁的实现细节,为面试提供关键知识点。
170万+

被折叠的 条评论
为什么被折叠?



