主要还是为了自己再记得清楚一点,加深印象,免得日后忘记。
Semaphore
semaphore主要提供了两个api。有点类似我们在购物通道中,只允许站下三个人,当第四个人过来的时候则要等待前面的人走掉。
//可以控制同时访问共享资源的线程个数
Semaphore semaphore = new Semaphore(3);
//通过 acquire() 方法获取一个信号量,信号量减 1,如果没有就等待;
semaphore.acquire();
release() 方法释放一个信号量,信号量加 1
semaphore.release();
1、new Semaphore(3);
提供一个有参构造函数。点击到最下层,会发现构造参数是给state设置初始值。
protected final void setState(int newState) {
state = newState;
}
2、semaphore.acquire();
//arg默认为1
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//如果不进入if判断,则程序正常执行。否则程序会进入到阻塞队列中
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
3、tryAcquireShared(arg)
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
当有一个线程进来的时候先获取state的值,第一次的时候state=3,也就是我们构造的初始值,acquires默认为1,这个时候remaining=3-1=2。这个时候remaining是大于0的,修改state值为2,然后返回2
当结果返回2的时候tryAcquireShared(arg) < 0
不成立,则不会进入if判断中,线程也就不会阻塞。当第二个线程进来的时候该段代码会返回1,第三个线程进来的时候会返回为0,都不会进入if中。当第四个线程进来的时候。此时state=0,remaining = 0-1=-1,这段代码则返回-1,那么tryAcquireShared(arg) < 0
条件成立了,则要进入if判断中。
4、doAcquireSharedInterruptibly(arg)
第四个线程会进入到该段代码中,首先node = new Node,看看链表是否初始化过了,没有初始化,则进行链表初始化,将该node节点放入到链表的尾部并返回,如果初始化过了,直接放在尾部并返回。然后再获取该节点的上一个节点p。(这里初始化双线链表的时候会新建一个空节点同时指向头结点和尾结点。也就是说头结点不存放一些其他信息。)
这里的p肯定就是头结点了。再去执行一下tryAcquireShared(arg);
这里肯定会返回小于0的值,所以也不会进入到判断中,这个时候需要进入到shouldParkAfterFailedAcquire(p, node)
方法中
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//初始化双向链表,将该节点放入链表尾部并返回,节点中存放当前线程
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取该节点的上一个节点
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
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);
}
}
addWaiter(Node.SHARED);相关源码
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
获取某节点的上一个节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
5、shouldParkAfterFailedAcquire(p, node)
上面说了,头结点初始化是个空节点,waitStatus默认为0,shouldParkAfterFailedAcquire(p, node)判断pred(我们这里是个头结点,假如有第五个线程进来,这个pred就是第四个线程)的waitStatus是不是-1,不是的话则修改为-1,返回false,则if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
这段代码不会进入if中,此时会继续执行 for (;;)
第二次进入到shouldParkAfterFailedAcquire(p, node)
的时候,由于上一次已经修改成-1了 所以会返回true,那么则会进入到判断中
6、 parkAndCheckInterrupt()
parkAndCheckInterrupt 这段代码调用 LockSupport.park(this);
第四个线程则会阻塞在这一行,等待被唤醒后才可以继续执行。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
以上便是semaphore.acquire();
阻塞的实现过程。
7、semaphore.release();
继续讲解线程唤醒代码实现过程
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
8、tryReleaseShared(arg)
在上面的时候前三个线程将state修改为0了,所以这里的state就是0,然后进行加1操作。返回true
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
9、doReleaseShared();
上面返回结果true的时候则会执行该方法。该方法的主要含义是唤醒头结点的下个节点。 if (h != null && h != tail)
这里感觉就是再判断一下,保证代码逻辑的安全可靠性。 if (h != null && h != tail)
判断一下头尾节点被初始化过了,也有节点内容在里面。再判断一下waitStatus是不是等于-1,再阻塞代码的时候就修改为-1了,所以这里是true的。compareAndSetWaitStatus(h, Node.SIGNAL, 0)
再把头结点的waitStatus修改为0,然后执行unparkSuccessor(h);
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;
}
}
10、unparkSuccessor(h);
s是头结点的下一个节点 也就是存放的第四个线程,此时进行唤醒
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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)
LockSupport.unpark(s.thread);
}
唤醒完之后,代码则会从上次阻塞的地方继续运行。继续在这个for循环执行。在release的时候state进行+1了,所以 int r = tryAcquireShared(arg)=0;
执行setHeadAndPropagate(node, r);
这就是把这个node节点设置为新的头结点,最终return,完成一个整的调用链
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
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);
}
}
CountDownLatch
CountDownLatch也有两个主要api。有点类似于等待其他人执行后自己才可以执行。其构造方法和Semaphore是一样的,都是初始化state值
//初始化state=3
CountDownLatch countDownLatch = new CountDownLatch(3);
//没调用一次则会state-1
countDownLatch.countDown();
countDownLatch.await();
1、 countDownLatch.await();
这里先讲await();方法的实现代码。也就是当tryAcquireShared(arg)
方法返回结果小于0的时候才会进入判断,那么则进行线程的阻塞等待,否则程序继续运行
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
2、tryAcquireShared(arg)
该代码的实现很简单,判断判断state值是否等于0,不等于0则返回-1,线程阻塞。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
3、doAcquireSharedInterruptibly(arg)
这块代码和Semphore的实现是一样的,将线程进行阻塞,然后等待唤醒
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
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);
}
}
4、countDownLatch.countDown();
最外层的实现代码如下。当tryReleaseShared(arg)
返回true的时候则会进入判断,回去唤醒线程。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
5、tryReleaseShared(arg)
这个方法很简单,就是判断state是不是等于0,不等于0的话,直接返回false,也不会去唤醒线程。否则进行state-1操作,然后判断state是不是0
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;
}
}
6、doReleaseShared()
只有当state值等于0的时候,才会去进行此操作。这段代码和Semphore的实现差不多,也是将头结点的waitStatus先修改为0,然后进行唤醒下一个节点的线程。唤醒之后从上次阻塞的地方继续执行这也没啥好说的。
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;
}
}
理解Semphore的源码之后,看这个就跟闹着玩一样了。