AQS源码解析
AQS帮我们干了啥?
- 内部定义了一个等待队列CLH队列,队列中每个节点持有一个线程。当当前线程抢占锁(tryAcquire)失败时,将当前线程阻塞并放到等待队列中;当锁被释放时,会唤醒等待队列的头结点,被唤醒的节点就会再次去尝试获取锁;
- 定义了state(int),compareAndSet(state),get(state) 获取或释放的标志位;
- ConditionObject 内部类是Condition接口的实现类
- AQS采用模板方法模式实现了获取、释放、入队/出队等主要逻辑,而真正子类只需要实现以下几个方法:
- tryAcquire(int arg) --定义独占锁获取逻辑
- tryRelease(int arg)-- 定义独占锁释放逻辑
- tryAcquireShared(int arg)--定义共享锁获取逻辑
- tryReleaseShared(int arg)--定义共享锁释放逻辑
- isHeldExclusively()--当前线程是否持有该独占锁
如何保证线程安全
volatile+CAS(CAS是通过调用unsafe.compareAndSwapObject实现)
CAS没有成功则阻塞当前线程,并将当前线程加入到CLH等待队列
如何实现线程阻塞和唤醒
LockSupport.park(Object blocker)
LockSupport.unpark(Thread thread)
底层调用Unsafe.park,是个native方法,非java实现
可以看到一个线程A想要唤醒另一个线程B,需要先去等待队列中拿到线程B,然后调用LockSupport.unpark(Thread threadB)
公平锁/非公平锁
tryAcquire的实现不一样,公平锁在获取锁之前会判断等待队列中是否还有Predecessors,非公平锁则是直接获取锁;
- 公平锁
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;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- 非公平锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
可重入锁/不可重入锁
ReentrantLock是常用的重入锁,其与不可重入锁的区别在于获取锁时先判断state,如果state>0 则继续判断获取锁的线程是否就是当前线程,如果是则获取锁并且state++,具体实现:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
独占锁/共享锁
ReentrantLock就是独占锁,同时只能有一个线程获取到锁,ReentrantReadWriteLock.ReadLock是共享锁,可以同时有多个线程获取到读锁,但如果写锁被获取时,则读锁也会阻塞等待,读写锁只有读读可以共享。
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();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
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;
}
return fullTryAcquireShared(current);
}
锁降级
将获取到的写锁降级为读锁,降级过程:
1.获取写锁
2.获取读锁
3.释放写锁
悲观锁/乐观锁
synchronized、ReentrantLock属于悲观锁实现,就是悲观的认为竞争环境恶劣,上来直接加锁,获取不到就阻塞线程,放入到阻塞队列中,这在linux中也叫非忙等待锁;
CAS则是乐观锁的实现,认为竞争环境没那么恶劣,所以不加锁,而是通过CAS+自旋来处理竞争情况,在linux中叫自旋锁,或者忙等待锁,所以竞争不激烈的情况下乐观锁效率更高。java中的原子类都是CAS+自旋实现,可以理解为乐观锁。
AQS的应用实例
CountDownLatch
CountDownLatch又叫计数器,他通过一个共享的计数总量来控制线程锁的获取,当计数器总量大于0时,线程将被阻塞,不能够获取锁,只有当计数器总量为0时,所有被阻塞的线程同时被释放。应用实例:
public class Starter {
private static final int NCPU = Runtime.getRuntime().availableProcessors();
private static CountDownLatch countDownLatch = new CountDownLatch(NCPU);
private static ThreadPoolExecutor threadPoolExecutor;
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("game-executor-%d").build();
initThreadPoolExecutor(threadFactory);
startGame();
loadData();
}
private static void initThreadPoolExecutor(ThreadFactory threadFactory) {
threadPoolExecutor = new ThreadPoolExecutor(NCPU, NCPU, 50L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(6), threadFactory, new ThreadPoolExecutor.AbortPolicy());
}
private static void startGame() {
threadPoolExecutor.execute(() -> {
try {
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "--startGame");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
private static void loadData() {
for (int i = 0; i < NCPU; i++) {
threadPoolExecutor.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + "--loadData");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
执行结果:
game-executor-1--loadData
game-executor-2--loadData
game-executor-3--loadData
game-executor-1--loadData
game-executor-0--startGame
countDownLatch是通过AQS的共享锁实现的,主要是await()和countDown()方法,
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
- tryAcquireShared方法是判断state是否到0了,如果还没有减到零,就通过doAcquireSharedInterruptibly(继承自AQS)获取共享锁;
- countDown方法是主要调用tryReleaseShared对state进行减1操作,如果减到0了,就调用doAcquireSharedInterruptibly(继承自AQS)释放共享锁,从而唤醒所有的阻塞线程;
Semaphore
Semaphore就像旋转寿司店,共有10个座位,当座位有空余时,等待的人就可以坐上去。如果有只有2个空位,来的是一家3口,那就只有等待。如果来的是一对情侣,就可以直接坐上去吃。当然如果同时空出5个空位,那一家3口和一对情侣可以同时上去吃。CountDownLatch就像大型商场里面的临时游乐场,每一场游乐的时间过后等待的人同时进场玩,而一场中间会有不爱玩了的人随时出来,但不能进入,一旦所有进入的人都出来了,新一批人就可以同时进场。juejin.im/post/684490…
CyclicBarrier
相对于CountDownLatch是一次性对象,一旦进入终止状态,就不能被重置,CyclicBarrier可以反复使用。CyclicBarrier类似于闭锁,与闭锁的关键区别在于,闭锁用于等待事件,栅栏用于等待其他线程,其作用是让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 juejin.im/post/684490…
synchronized底层原理
线程状态转换
Thread.join()底层实现是Object.wait()所以状态是waiting,会释放锁资源
Future.get()底层实现是LockSupport.lock()所以状态也是waiting
Thread.yield()线程状态变成ready,释放cpu资源,但不会释放锁资源