持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情
一、Semaphore初始化。
Semaphore semaphore=new Semaphore(2);
- 当调用new Semaphore(2) 方法时,默认会创建一个非公平的锁的同步阻塞队列。
- 把初始令牌数量赋值给同步队列的state状态,state的值就代表当前所剩余的令牌数量。
二、获取令牌
semaphore.acquire();
- 判断是否满足获取锁条件, 关键方法
nonfairTryAcquireShared() - 若获取锁成功,则也会修改
state - 若获取锁失败,关键方法
doAcquireSharedInterruptibly()阻塞的获取锁
本质上是调用的AQS#acquireSharedInterruptibly(int), 参数为1
1、当前线程会尝试去同步队列获取一个令牌,获取令牌的过程也就是使用原子的操作去修改同步队列的state ,获取一个令牌则修改为state = state-1。
2、 当计算出来的state<0,则代表令牌数量不足,此时会创建一个Node节点加入阻塞队列,挂起当前线程。
3、当计算出来的state>=0,则代表获取令牌成功。
/**
* 获取1个令牌
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程
* @param arg
* @throws InterruptedException
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
/**
* 1、创建节点,加入阻塞队列,
* 2、重双向链表的head,tail节点关系,清空无效节点
* 3、挂起当前节点线程
* @param arg
* @throws InterruptedException
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//创建节点加入阻塞队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获得当前节点pre节点
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);//返回锁的state
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//重组双向链表,清空无效节点,挂起当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
三、释放令牌
- 释放N个许可, 因为存在并发释放, 需要CAS确保设置更新后的值
- 唤醒双向链表中有效的等待节点(可能存在并发问题,引入PROPAGATE状态)
- 被唤醒的节点调用获取锁的流程
semaphore.release();
semaphore.release(int) 释放int个许可
两个方法都会调用AQS#releaseShared(int)方法, 使用release()方法,则参数为1, 使用release(int)方法, 则参数为int。
当调用semaphore.release() 方法:
1、线程会尝试释放一个令牌,释放令牌的过程也就是把同步队列的state修改为state=state+1的过程 2、释放令牌成功之后,同时会唤醒同步队列中的一个线程。 3、被唤醒的节点会重新尝试去修改state=state-1 的操作,如果state>=0则获取令牌成功,否则重新进入阻塞队列,挂起线程。
/**
* 释放令牌
*/
public void release() {
sync.releaseShared(1);
}
/**
*释放共享锁,同时会唤醒同步队列中的一个线程。
* @param arg
* @return
*/
public final boolean releaseShared(int arg) {
//释放共享锁
if (tryReleaseShared(arg)) {
//唤醒所有共享节点线程
doReleaseShared();
return true;
}
return false;
}
/**
* 唤醒同步队列中的一个线程
*/
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))//修改状态为初始0
continue;
unparkSuccessor(h);//唤醒h.nex节点线程
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
}
if (h == head) // loop if head changed
break;
}
}