文章目录
- 1.简介
- 2.通过可重入锁(ReentrantLock)来探究AQS的实现原理
- 2.1 对构造方法的解析
- 2.2 对lock()方法的解析
- 2.2.1 公平锁时
- 2.2.2 非公平锁
- 2.3 unLock方法
- 2.4 对ReentrantLock的总结
- 3.根据可重入读写锁(ReentrantReadWriteLock)来探究AQS的源码
- 3.1 构造方法的分析
- 3.2 对读锁的分析
- 3.2.1 lock方法源码分析
- 3.2.2 释放锁unLock
- 3.3 对写锁的分析
- 3.3.1 lock方法
- 3.3.2 unlock方法
- 3.3.3 总结
- 4.synchronized与AQS的对比
1.简介
AQS(AbstractQueuedSynchronizer)是JDK1.5开始提供的一个基于一个等待队列(FIFO)实现的阻塞锁(Lock)和同步器(Semaphores,event等)的一个框架。是一个抽象类这个类被设计做为许多同步器的基础,而这些同步器依赖于一个原子类型的int值state(state = 0表示锁未被线程使用,state>0时表示锁被线程持有,state的值表示这个锁被获取的次数(例如:重入锁允许一个线程多次加锁,每加一次锁state的值加1))。
在AQS中定义了许多受保护的方法这些方法只抛出异常,而没有具体实现。其具体实现应该由子类来根据不同场景实现。而这些受保护的方法会改变state的状态并且定义了对象是如何获取和释放锁。AQS它提供了三个方法 getState()、setState(int newState)、compareAndSetState(int expect, int update)来对同步状态state进行操作,当然AQS可以确保对state的操作是线程安全的。
AQS中一些重要的受保护方法:
- tryAcquire:独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态;
- tryRelease:独占式释放同步状态;
- tryAcquireShared:共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
- tryReleaseShared:共享式释放同步状态;
- isHeldExclusively:如果是排它锁返回true,如果是共享锁返回false,该方法判断锁的类型。
在AQS中存在两个内部类ConditionObject和Node:
- ConditionObject:该类继承类Condition实现了线程的休眠和唤醒,在底层维护了一个等待队列
- Node:维护了一个FiFo的双向队列,存放阻塞的线程。CLH同步队列
2.通过可重入锁(ReentrantLock)来探究AQS的实现原理
先看看ReentrantLock的内部类Sync(继承了AQS)中的实现的方法
NoFairSync和FairSync都是继承自Sync,可以看到Sync及其子类把有几个重要的所保护方法都实现了tryAcquireShared没有实现是因为不需要,因为ReentrantLock是排他的。
从上面还能得出两个信息,ReentrantLock的锁分为公平锁和非公平锁
- 公平锁(FairSync):新加入的线程会放在FIFO的队尾等待获取执行权
- 非公平锁(NoFairSync):新加入的线程会先和FIFO队列的队头线程竞争锁,如果竞争到锁就执行,而堆头线程等待下一次竞争锁,如果没有竞争到锁,就把该线程加入到FIFO队列的队尾。
对ReentrantLock的使用:
public class AQSDemo {
private ReentrantLock readWriteLock = new ReentrantLock();
public void method() {
try {
readWriteLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method");
} finally {
readWriteLock.unlock();
}
}
public static void main(String[] args) {
AQSDemo myTest2 = new AQSDemo();
IntStream.range(0, 10).forEach(i -> new Thread(myTest2::method).start());
}
}
2.1 对构造方法的解析
ReentrantLock默认使用非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
如果需要自己指定锁的类型可以使用另一个构造方法
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
传入true表示使用公平锁,传入false表示所有非公平锁。
2.2 对lock()方法的解析
2.2.1 公平锁时
public void lock() {
sync.lock();
}
//调用FairSync的lock方法
final void lock() {
acquire(1);
}
//调用AQS中的acquire方法
public final void acquire(int arg) {
//通过tryAcquire方法尝试获取锁,如果获取到锁线程正常执行,如果获取不到是
//就打断正在执行的线程
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//调用FairSync的tryAcquire方法
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
//获取当前线程对象
final Thread current = Thread.currentThread();
int c = getState();
//state为0表示锁未被其他线程获取,直接加锁
if (c == 0) {
//hasQueuedPredecessors判断阻塞队列FIFO是否有元素,如果没有返回false
if (!hasQueuedPredecessors() &&
//对state执行+1操作
compareAndSetState(0, acquires)) {
//如果上面两项判断通过就把当前线程设置一个排他锁
setExclusiveOwnerThread(current);
return true;
}
}
//c不等于0,说明已经有线程获取了锁,
//获取当前的排它锁线程,因为ReentrantLock是可重入锁
//故当前线程与getExclusiveOwnerThread相等说明他们是同一个
//线程,就把state+1然后赋值给state
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁加锁时只有满足第一次加锁且阻塞队列没有元素才允许加锁(公平判断),或要加锁的线程是上一次加锁的线程。
2.2.2 非公平锁
public void lock() {
sync.lock();
}
//进入NoFairSync的lock方法
final void lock() {
//直接对state+1,如果成功直接设置一个排它锁
//失败调用acquire方法
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//AQS的acquire方法
public final void acquire(int arg) {
//尝试加锁并进行相关的判断,如果失败打断线程,
//并且把线程加入阻塞队列的队尾,如果成功线程继续执行
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//进入NoFairSync的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//进入Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果锁没有被线程获取
if (c == 0) {
//直接把state加1,如果成功就把当前线程设置为排他线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果已被加锁,则于公平锁进行的操作相同
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
通过源码分析可知非公平锁没有对阻塞队列是否有值进行判断而是直接加锁,符合我们对非公平锁的描述。
2.3 unLock方法
公平锁和非公平锁区别是在加锁,而不是释放锁,故二者释放锁的存在相同。
public void unlock() {
sync.release(1);
}
//进入AQS的release方法
public final boolean release(int arg) {
//tryRelease是对state是否为0进行判断,如果为0返回true
//说明锁被释放,可以唤醒等待队列的线程获取锁
if (tryRelease(arg)) {
Node h = head;
//获取等待队列的头因为第一个是空的
//所以唤醒的是头节点的后一个节点中的线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//进入Sync的tryRelease方法
protected final boolean tryRelease(int releases) {
//获取state-1的值
int c = getState() - releases;
//做一个判断,只有加锁的线程才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//state=0返回true,不为0返回false
if (c == 0) {
free = true;
//释放排它锁线程
setExclusiveOwnerThread(null);
}
//更新state的值
setState(c);
return free;
}
//进入AQS的release方法
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//唤醒FIFO队列的第一个打断线程
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//调用底层c++的方法来对线程进行唤醒
LockSupport.unpark(s.thread);
}
unLock释放锁后如果锁未被其他线程持有就会唤醒等待队列FIFO的第一个线程,来获取锁。
2.4 对ReentrantLock的总结
对于ReentrantLock来说,其执行逻辑如下所示:
- 尝试获取对象的锁,如果获取不到(意味着已经有其他线程持有了锁,并且尚未释放),那么它就会进入到AQS的阻塞队列当中。
- 如果获取到,那么根据锁是公平锁还是非公平锁来进行不同的处理
2.1 如果是公平锁,那么线程会直接放置到AQS阻塞队列的末尾
2.2 如果是非公平锁,那么线程会首先尝试进行CAS计算,如果成功,则直接获取到锁; 如果失败,则与公平锁的处理方式一致,被放到阻塞队列末尾 - 当锁被释放时(调用了unlock方法),那么底层会调用release方法对state成员变量值进行减一操作,如果减一后,state值不为0那么release操作就执行完毕;如果减一操作后,state值为0,则调用LockSupport的unpark方法唤醒该线程后的等待队列中的第一个后继线程(pthread_mutex_unlock),将其唤醒,使之能够获取到对象的锁(release时,对于公平锁与非公平锁的处理逻辑是一致的);之所以调用release方法后state值可能不为零,原因在于ReentrantLock是可重入锁,表示线程可以多次调用lock方法,导致每调用一次,state值都会加一。
对于ReentrantLock来说,所谓的上锁,本质上就是对AQS中的state成员变量的操作:对该成员变量+1,表示上锁;对该成员变量-1,表示释放锁。
3.根据可重入读写锁(ReentrantReadWriteLock)来探究AQS的源码
对读写锁的使用
public class AQSDemo {
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void method() {
try {
readWriteLock.readLock().lock();
// readWriteLock.writeLock().lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method");
} finally {
// readWriteLock.writeLock().unlock();
readWriteLock.readLock().unlock();
}
}
public static void main(String[] args) {
AQSDemo myTest2 = new AQSDemo();
IntStream.range(0, 10).forEach(i -> new Thread(myTest2::method).start());
}
}
结果:
method
method
method
method
method
method
method
method
method
method
如果使用读锁进行加锁,和释放锁,那么会一次把所有内容都打印出来,如果使用写锁那么会一次大于。
关于读写锁:
- 读锁:因为读一个数据不会对数据改变,所以为了多少个线程读取都无所谓,故如果是一个线程获取读锁,那么其他线程仍然能获取读锁。读锁是一个共享锁
- 写锁:对变量的写操作,必须具备原子性。故写锁是一个不可重入的排它锁,如果锁已经被获取(state变量不为0)那么无论加的是什么锁,读锁都会加锁失败。只有(state=0时)写锁才能获取锁。
读写锁公用一个state,因为state是一个int类型,故其高16位表示读锁,低16位表示写锁,被定义在ReentrantReadWriteLock的内部类Sync中
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
3.1 构造方法的分析
public ReentrantReadWriteLock() {
//默认非公平锁
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
//无论读写锁都是使用了ReentrantReadWriteLock的Sync对象
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
ReentrantReadWriteLock也分为公平锁和非公平锁。同时在声明ReentrantReadWriteLock对象时也声明了读锁和写锁,读写锁的Sync都是使用了ReentrantReadWriteLock的Sync对象。
3.2 对读锁的分析
3.2.1 lock方法源码分析
public void lock() {
sync.acquireShared(1);
}
//进入AQS的acquireShared方法
public final void acquireShared(int arg) {
//尝试加读锁,如果失败尝试其他方式加锁,只要没有加写锁就一定要保证
//读锁加锁成功
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//进入sync的tryAcquireShared方法
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
//判断,exclusiveCount获取的是写锁的state数
//如果写锁加了。那么判断线程是否是加了写锁的线程就返回-1
//有其他部分尝试加锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取写锁的数量
int r = sharedCount(c);
//readerShouldBlock是公平锁还是非公平锁的判断,放在无论是公平还是非公平
//最终都会加锁
//锁的数量不能大于(1 << 16) - 1,因为读锁只占32位
//对加锁数进行CAS的加一操作
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// r=0,说明是第一个加的读锁,使用firstReader 存储,并经历其
//firstReaderHoldCount
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
//不是第一次加锁,但是是第一次加锁的线程,直接firstReaderHoldCount++
} else if (firstReader == current) {
firstReaderHoldCount++;
//不是第一次加锁,其线程被第一次加锁的线程
} else {
//把其相关信息存入其ThreadLocal中
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//这是CAS判断失败是做的补救措施
return fullTryAcquireShared(current);
}
即如果加的是读锁会确保最后线程一定加锁成功。
3.2.2 释放锁unLock
public void unlock() {
sync.releaseShared(1);
}
//进入AQS的releaseShared方法
public final boolean releaseShared(int arg) {
//尝试释放锁,如果state=0返回true释放所有持有锁的线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//进入sync的tryReleaseShared方法
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//如果第一个加读锁的线程是当前线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
//且第一次线程加了一次锁
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//如果当前释放写锁线程不是第一个加写锁线程
//HoldCounter 存储在每个线程的ThreadLocal中
//记录每个线程加锁的次数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
//说明当前线程只加了一次锁,就把ThreadLocal释放掉
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
//加了多次锁,把计数减一
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
//如果state为0就把所有持有锁线程释放。否则单释放
//读锁没有效果不做任何处理,因为读锁释不释放都不影响结果
return nextc == 0;
}
}
释放锁,如果说最后一个持有锁的线程释放锁就把所有持有锁的线程释放,否则只修改修改的持有锁的线程的持有锁次数。
3.3 对写锁的分析
3.3.1 lock方法
public void lock() {
sync.acquire(1);
}
//进入AQS的acquire方法
public final void acquire(int arg) {
//尝试加锁,如果不成功就把线程加入到等待队列中。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//进入sync的tryAcquireShared方法
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
//获取写锁的state数量
int w = exclusiveCount(c);
if (c != 0) {
//锁已经被获取,且持有锁的线程不是当前线程,加锁失败
//即加读锁的情况
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超过能加锁的最大数量
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//加可重入锁,即加锁的是当前线程
setState(c + acquires);
return true;
}
//尝试加写锁,且CAS的改变state
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//CAS成功且加锁成功,把持有锁的线程设置为当前线程
setExclusiveOwnerThread(current);
return true;
}
加读锁时如果已经加了读锁就会失败,因为写锁是排它锁。
3.3.2 unlock方法
public void unlock() {
sync.release(1);
}
//进入AQS的release方法
public final boolean release(int arg) {
//尝试对写锁进行释放,如果释放锁后state=0,就进行唤醒等待队列中的线程
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒等待队列中第一个线程
unparkSuccessor(h);
return true;
}
return false;
}
//进入sync的tryRelease方法
protected final boolean tryRelease(int releases) {
//判断是不是排它锁,如果不是抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取state变量-1的值
int nextc = getState() - releases;
//判断释放线程锁后,锁是否没有被其他线程持有
//如果线程释放锁后,锁未被线程持有则把排它锁线程设置为null
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
//进入AQS的unparkSuccessor方法
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//unpark会调用unsafe类的unpark方法,然后使用c++
//语言对等待队列中第一个线程进行唤醒
LockSupport.unpark(s.thread);
}
作用就是释放锁,如果释放后state=0就唤醒等待队列中的元素。
3.3.3 总结
关于ReentrantReadWriteLock的操作逻辑:
读锁:
- 在获取读锁时,会尝试判断当前对象是否拥有了写锁,如果已经拥有,则直接失败。
- 如果没有写锁,就表示当前对象没有排他锁,则当前线程会尝试给对象加锁
- 如果当前线程已经持有了该对象的锁,那么直接将读锁数量加1
写锁:
- 在获取写锁时,会尝试判断当前对象是否拥有了锁(读锁与写锁),如果已经拥有且持有的线程并非当前线程,直接失败。
- 如果当前对象没有被加锁,那么写锁就会为为当前对象上锁,并且将写锁的个数加1.
- 将当前对象的排他锁线程持有者设为自己
4.synchronized与AQS的对比
关于AQS与synchronized关键字之间的关系:
- synchronized关键字在底层的C++实现中,存在两个重要的数据结构(集合):WaitSet, EntryList
- WaitSet中存放的是调用了Object的wait方法的线程对象(被封装成了C++的Node对象)
- EntryList中存放的是陷入到阻塞状态、需要获取monitor的那些线程对象
- 当一个线程被notify后,它就会从WaitSet中移动到EntryList中。
- 进入到EntryList后,该线程依然需要与其他线程争抢monitor对象
- 如果争抢到,就表示该线程获取到了对象的锁,它就可以以排他方式执行对应的同步代码。
- AQS中存在两种队列,分别是Condition对象上的条件队列,以及AQS本身的阻塞队列
- 这两个队列中的每一个对象都是Node实例(里面封装了线程对象)
- 当位于Condition条件队列中的线程被其他线程signal后,该线程就会从条件队列中移动到AQS的阻塞队列中。
- 位于AQS阻塞队列中的Node对象本质上都是由一个双向链表来构成的。
- 在获取AQS锁时,这些进入到阻塞队列中的线程会按照在队列中的排序先后尝试获取。
- 当AQS阻塞队列中的线程获取到锁后,就表示该线程已经可以正常执行了
- 陷入到阻塞状态的线程,依然需要进入到操作系统的内核态,进入阻塞(park方法实现)