AQS共享锁的实现原理以
Semaphore
为例
Semaphore
控制访问特定资源的线程数目(permits)。可用场景:资源访问、服务限流。
//构造方法,默认fair为false,即非公平锁。
Semaphore(int permits)
Semaphore(int permits, boolean fair)
获取资源
- Semaphore初始化的时候就将
state
的值设置为permits
,默认为非公平锁。 - 当有线程尝试获取锁时,会使用死循环(高并发下进行大量CAS会有失败的情况)将
state-1
表示可用数量,如果可用数量大于0,则进行CAS操作,将state设置为可用数量。 - 当超过
permits
数量的线程池尝试获取锁时,此时可用数量将会小于0,此时将会返回一个负数的可用数量。
相关源码赏析:
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
- 当可用数量小于0时,会
将线程以共享(Node.SHARED)的方式插入到CLH队列中
。第一个入队的线程会先创建一个空的Node
,且尾部节点等于头部节点,后续入队的线程会将当前线程节点的prev指向上一个的tail,并用CAS操作将上一个的tail指向当前节点,相关源码在java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
方法中。 - Node初始化时状态为0,入队后会通过
java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
方法移除cancel状态的节点,并通过CAS操作将当前节点状态置为signal。之后续会将此线程阻塞。
释放资源
- 与上面相反,state会加上释放资源的数量,并通过CAS操作更新state。
- 将CLH头节点由SIGNAL状态更改为0,并将头结点下一个节点状态不为CANCEL状态的节点唤醒。让其去竞争锁资源。注意:如果头结点本身的状态为0,会将其先设置为
PROPAGATE
(传播)状态,意味着状态需向后一个节点传播。
相关源码赏析:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
CountDownLatch
使一个线程等待其他线程各自执行完毕后再执行。比如:和朋友出去玩时,一个人去景区买票,一个人买零食,集合后再一起进景区。
工作原理
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
使用示例
public static void main(String[] args) throws InterruptedException {
long st = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(2);
//假设业务1执行3s
new Thread(new Task1(countDownLatch)).start();
//假设业务2执行5s
new Thread(new Task2(countDownLatch)).start();
countDownLatch.await();
log.info("all task executed in {} millSeconds, we can start next task", System.currentTimeMillis() - st);
}
结果验证:
CyclicBarrier
与CountDownLatch
类似,不同的是CyclicBarrier
没有主次之分。
CountDownLatch可以看成是单个任务做完后进行汇总,而CyclicBarrier是所有任务做完后一起汇总。
注意:如果CyclicBarrier构造函数中的parties的值大于任务数,会一直等待。
Executors
线程初始化工具类
Exchanger
线程间数据交换。