网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
到这再强烈建议大家先入手AQS,入手了之后再看这并发包下的工具类真的不要太简单!!!
countDown()图示、源码讲解
再来看看 countDown 方法
public void countDown() {
sync.releaseShared(1);
}
这里调用了sync的releaseShared方法,传入了arg = 1
而我们可以看到releaseShared涉及到了两个方法 tryReleaseShared(1)和doReleaseShared()
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
先看tryReleaseShared(1)
这源码相信给位小伙伴一看都能懂把,那我就简要总结下!
- 调用getState()方法获取同步状态,如果同步状态为0,则返回false,结合下面的代码看,意味着不能在减同步状态了
- 如果不为0,也可以说是大于0,那么用一个int变量记录将同步状态减一后的值
- 最后同步CAS设置同步状态为减一后的值,如果设置失败就自旋重试,如果成功就看减一后的值是不是0
不了解CAS的也可以看看我的这篇博客,超高点击量:面试被问到CAS原理,触及知识盲区,脸都绿了!
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;
}
}
结合之前源码看,如果减一后的同步状态为0,那么就会调用 doReleaseShared()方法
private void doReleaseShared() {
for (;;) {
//获取头结点
Node h = head;
if (h != null && h != tail) {
//获取head结点的等待状态
int ws = h.waitStatus;
//如果head结点的状态为SIGNAL, 表明后面有人在排队
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
//直到CAS成功
continue;
//再去唤醒后继结点;在独占锁的时候有说明,这里就不多说了;
unparkSuccessor(h);
}
//如果head结点的状态为0, 表明此时后面没人在排队, 就只是将head状态修改为PROPAGATE
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//直到CAS成功
continue;
}
if (h == head)
break;
}
}
可能小伙伴们看了源码也没太懂,那我小小总结一下
- 当同步状态为0的时候才会去调用,doReleaseShared()方法
- 如果同步状态为0,说明锁没有线程占用
- 那么就涉及到doReleaseShared()方法,去看该线程后面有没有线程排队
- 如果有线程排队,那么upark将其唤醒,并且有没有节点都要更新当前节点的状态
await()图示、源码讲解
await()方法调用的 sync的acquireSharedInterruptibly(1)方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
点进来看
第一个if没啥好说的,如果线程被打断了就要抛异常
主要是第二个if,涉及到tryAcquireShared(1),doAcquireSharedInterruptibly(1)方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared(1)跟传入的参数无关,就是看当前的同步状态是否为0,如果为0返回1,不为0返回-1
那么有小伙伴问了,为什么要这么返回呢???
不要着急,我们接着看第二个方法
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
doAcquireSharedInterruptibly
别的都可以不看,关键就在于这个for(;;)循环
我们可以看到,如果想要退出for循环,必须满足 (p == head) && (r >= 0),也就是当前节点等于头节点,且同步状态为0
tryAcquireShared我们上面介绍过
温馨提示:建议了解AQS:AQS快速上手
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);
}
}
ok,相信能看到这里的小伙伴可能差那么一点就能明白,那我再给各位小伙伴总结一下。
创作不易,希望小伙伴们可以一键三连多多支持!!! 😁
总结
就像我开头说的,
调用await()方法的线程会被阻塞,直到计数器 减到 0 的时候,才能继续往下执行;
countDown():将计数器减一。
结合上面的源码讲解,先说await()
- 如果计数器不为0,那么tryAcquireShared返回的就一定为1,那么
r >= 0
就不会满足,那么就无法退出,会一直进行for循环即起到阻塞作用
再说countDown()
-
每调用一次countDown()方法就会去利用CAS将计数器减一
-
当同步状态为0的时候才会去调用,doReleaseShared()方法
- 如果同步状态为0,说明锁没有线程占用
-
那么就涉及到doReleaseShared()方法,去看该线程后面有没有线程排队
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
715782397721)]
[外链图片转存中…(img-HvdXVxSf-1715782397721)]
[外链图片转存中…(img-4EgcC5Yn-1715782397721)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新