JDK 1.8.0_152 java.util.concurrent.locks 包下有:AbstractOwnableSynchronizer
AbstractQueuedLongSynchronizer
AbstractQueuedSynchronizer
Condition
Lock
LockSupport
ReadWriteLock
ReentrantLock
ReentrantReadWriteLock
StampedLock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~AbstractOwnableSynchronizer|                  |
AbstractQueuedLongSynchronizer     AbstractQueuedSynchronizer(抽象,且没有实现类)||本类的实现类很多,但都是在其他类的内部类实现。Subclasses should be defined as non-public internal helper classes (java doc)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Condition||-------------------------------           |                             |
AbstractQueuedLongSynchronizer::ConditionObject     |     |         AbstractQueuedSynchronizer::ConditionObject    Condition 有两个实现了,都是别人的内部类中实现的
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Lock|-----------------------------------------------------------|                     |           |                       |
StampedLock::ReadLockView     |           |     ReentrantReadWriteLock::ReadLock
StampedLock::WriteLockView    |           ||           |ReentrantReadWriteLock::WriteLock  ||ReentrantLock            |ConcurrentHashMap::Segment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ReadWriteLock|        |StampedLock::ReadWriteLockView        ReentrantReadWriteLock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
其他未标记的是没有特殊说明的继承关系。
不过看到这些继承关系还是不知道从哪里开始看起。那就从头开始看,如果是抽象类那就
找到它的子类,从他的子类开始,先要用起来,才能知道那些源码想要干什么。其中 AbstractQueuedSynchronizer 的实现类 有一个是在 CountDownLatch 中实现。
而且 CountDownLatch 的功能不是那么多,所以 CountDownLatch 是研究锁的突破口。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
使用代码:
设置一个倒计时,总是为2,
开启两组线程, 第一组2个线程, 休眠以后,然后倒计时
第二组3个线程,全部阻塞,等待着倒计时结束。 倒计时结束后,三个线程开始后续的执行。
private static void t1()
{int count = 2;CountDownLatch countDownLatch = new CountDownLatch(count);for (int i=0; i< count; ++i) {new Thread(() -> {//countDownLatch.getCount() 获取当前还有几个数System.out.println("before:" + countDownLatch.getCount());sleep((int) (Math.random() * 5000));countDownLatch.countDown();   //会让当前倒计时减1System.out.println(Thread.currentThread().getId() + "-结束:" + System.currentTimeMillis());System.out.println("after:" + countDownLatch.getCount());}).start();}for (int i=0; i<3; ++i) {new Thread(()-> {try {//阻塞, 当倒计时为0时这里会被唤醒,再次执行countDownLatch.await();//还有一个限时阻塞的,超时了还没有倒计时结束,被唤醒,继续执行//countDownLatch.await(xxx);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("开始执行:" + System.currentTimeMillis());}).start();}System.out.println("main:" + System.currentTimeMillis());
}输出:
before:2
before:2
main:1613032130374
12-结束:1613032132511
after:1
13-结束:1613032134281
开始执行:1613032134281
开始执行:1613032134281
after:0
开始执行:1613032134281
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CountDownLatch 暴露的接口上面基本都涉及到了。
CountDownLatch 内部有一个类 Sync, CountDownLatch 的功能都是 Sync 实现的。Sync 的实现代码少到让我感到惊讶!private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}
}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CountDownLatch countDownLatch = new CountDownLatch(count);
结果就是 创建 Sync 对象,调用 setState(count);AbstractQueuedSynchronizer::setState// private volatile int state;  // state 的定义protected final void setState(int newState) {state = newState;}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
countDownLatch.await();public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}AbstractQueuedSynchronizer::acquireSharedInterruptiblypublic final void acquireSharedInterruptibly(int arg) throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
下面这个方法被 Sync 复写(必须复写,因为 AbstractQueuedSynchronizer 中这个方法抛异常)
参数 acquires 是 Sync 前面传入的 1, 应该没有什么用。
返回值:-1: 负数表示 失败0: 本次成功,后续xxx不成功1:  正数,表示本次成功,后续的xx也可能成功不太明白,继续吧
protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;
}显然第一次调用 await 时 Sync::tryAcquireShared 返回了-1
然后就调用了 AbstractQueuedSynchronizer::doAcquireSharedInterruptiblyprivate void doAcquireSharedInterruptibly(int arg) throws InterruptedException {final Node node = addWaiter(Node.SHARED);     //---------------1boolean failed = true;try {for (;;) {//获取自己加入的 node 的前一个节点。final Node p = node.predecessor();if (p == head) {//自己的前节点就是 head, 再次尝试获取一下资源,//countDownLatch 倒计时还没结束, 所以这个方法返回值小于0 int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//代码在 ---3 中阻塞,如果阻塞解除,这里的if 没有进去,则继续死循环了if (shouldParkAfterFailedAcquire(p, node) &&  //---------------2parkAndCheckInterrupt())                  //---------------3throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}---------------1
/*** Creates and enqueues node for current thread and given mode.** @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared* @return the new node*/
private Node addWaiter(Node mode) {//Node 内部类,不必细看,Node 内部有 前对象 和 后对象, 他们连接能形成双向链表Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failure//AbstractQueuedSynchronizer 类中定义了两个 Node 类型的变量, head 和 tail//很明显 刚刚创建的这个 Node 要加到 head 和 tail 中。Node pred = tail;if (pred != null) {//新创建的 Node 要加到后面,变成新的 tail, 而原来的tail变成了倒数第二个//所以 新创建的 node 的前一个是 原来的tail. 但是并没着急把原 tail 的//后一个设置为新加入的 node。 因为需要更新 tail 为新加入的 node。 如果//更新失败,那不就把原来的 tail 污染了吗。node.prev = pred;if (compareAndSetTail(pred, node)) {//把新加入的 node 设置为 tail, 多线程竞争设置,只有设置成功的//才能把自己 新加入的 node 设置为 原tail 的后一个。到这里就把//原tail的 和 新Node 连接在一起, 即原tail 的下一个为新的 node。//新 加入的 node 是新的 tailpred.next = node;return node;}}//来到这里说明: tail 可能为空(链表还不存在), 或者 tail 存在,但是//竞争设置自己创建的 node 为新的 tail 时,竞争失败了enq(node);      //---------------1.1return node;
}---------------1.1:
//当前还没有tail, 或者有tail, 但是自己竞争失败了
//竞争失败,说明想把自己创建的 node 设置成 tail, 但是被别的线程捷足先登。
//这时不要生气, 把自己创建的 node 继续添加到被人抢先添加 node 的后面即可
// enq 的返回值是 添加的 node 的前一个 node, 而不是 node 本身, 但是返回值没人用
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initialize//没有 tail, 说明链表不存在, 先创建一个 head, //并让 tail 和 head 指向同一个对象, 而且成不成功都需要再循环一次。//因为 新Node 才是真正的数据。这里是创建 head, head 不是真正数据,//而是标记数据(找到head,tail就找到了链表)if (compareAndSetHead(new Node()))tail = head;} else {//被人抢先了,不要紧,这里是循环,直到把自己创建的 node 放到 tail 上node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------2
//参数:pred 是刚刚新创建的节点的前一个节点
//参数:node 是刚刚新创建的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)//ws 是前节点状态, 如果是 SINGAL 就直接返回 truereturn true;if (ws > 0) {//如果前节点状态大于0, 那么前节点的前节点作为自己的新的前节点//如果这个新的前节点的状态还是大于0, 继续把它的前节点作为自己的前节点//直到找到一个前节点不大于0的节点作为自己的前节点。//所以这里的代码意思是 删除状态大于0的前节点  //为什么不用考虑 一直循环导致 pred 变成 null 的问题呢。因为最前面的是 head,//而head的这个变量是 默认为0的,而且不会有大于0的情况do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {//把前节点状态从 0或者非 Node.SIGNAL 的状态通通改成 Node.SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}
还是没明白这到底在干什么:
补充:waitStatus 的状态有 下面4个和一个默认值 0CANCELLED =  1;SIGNAL    = -1;CONDITION = -2;PROPAGATE = -3;所以有效的是值是小于0, 大于0是表示被取消。  0 默认值,不表示意义SINGAL javaDoc: waitStatus value to indicate successor''s thread needs unparking一个链表上有多个节点,一个节点的状态是 SINGAL 表明本节点的下一个节点已经在睡觉或者即将要睡觉,而且本节点被唤醒时或者本节点被取消时有义务唤醒一下自己的下一个节点。所以这个方法的作用就是,一个线程想要阻塞了,但是自己在阻塞前必须要预定一个叫醒服务。
这样才能安心睡觉。如果一直没预定成功,返回了false, 调用本方法的是一个循环,一定要预定。
所谓的预定叫醒服务就是给自己的前一个节点的状态设置成 SINGAL 。 如果前一个节点的状态
大于0, 表示这个节点已经取消,它不靠谱了,不能找到,继续向前找,实在不行就找到 head 了。
head 的默认值时 0, 而且不可能被设置成大于0的值
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------3
这里的代码想要被执行要求 ---------------2 返回 true 才可以。 ----2 返回true则
又说明 新加入的节点(本节点)的前一个节点的 状态已经是 SINGAL。
然后本节点才能来到这里阻塞。阻塞的解除也有多个方式,这里是通过 unsafe.park 阻塞的。
可以 unsafe.unpark 解除, 也可以 Thread.interrupted() 打断。
因为 ----2 返回 true, 所以如果这里是因为 Thread.interrupted() 解除阻塞的。
这里也会返回 true, 然后执行就是抛出一个异常。
如果这里是 unsafe.unpark 正常解除阻塞, 这里返回 false,接下来 就不会抛出异常了//看看本节点是如何被阻塞的:
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
}代码来到: LockSupport 类中public static void park(Object blocker) {Thread t = Thread.currentThread();// Thread 类中有一个变量,表示是谁阻塞的本线程,// 这就是通过内存地址方式,把 blocker 设置进去setBlocker(t, blocker);//代码在这里永久的阻塞了, UNSAFE.park 在其他笔记中讲过了UNSAFE.park(false, 0L);//从上面的阻塞中恢复了,把阻塞本线程的对象清除掉setBlocker(t, null);
}//设置线程中的一个变量的值。这个变量代表着线程是被哪个对象阻塞的。
private static void setBlocker(Thread t, Object arg) {// Even though volatile, hotspot doesn't need a write barrier here.UNSAFE.putObject(t, parkBlockerOffset, arg);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
到了这里:多个调用 countDownLatch.await(); 的线程,都在链表中添加了自己 Node,
而且把前置 Node 的状态变成 SINGAL, 并且自己阻塞在 UNSAFE.park(false, 0L); 上。
如何解除阻塞,还要看 countDownLatch.countDown(); 的调用
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

JUC--005--locks1相关推荐

  1. 面试高频——JUC并发工具包快速上手(超详细总结)

    目录 一.什么是JUC 二.基本知识 2.1.进程和线程 2.2.Java默认有两个进程 2.3.Java能够开启线程吗? 2.4.并发和并行 2.5.线程的状态 2.6.wait和sleep的区别 ...

  2. JUC 常用 4 大并发工具类

    欢迎关注方志朋的博客,回复"666"获面试宝典 什么是JUC? JUC就是java.util.concurrent包,这个包俗称JUC,里面都是解决并发问题的一些东西 该包的位置位 ...

  3. JUC AQS ReentrantLock源码分析

    Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还 ...

  4. java应用menchac_java之JUC

    java的JUC即java.util.concurrent包,这是一个处理线程的工具包. 一.synchronized与lock锁 两者区别? 1.synchronized是java关键字,lock是 ...

  5. juc java_深入理解JUC(java.util.concurrent)

    Concurrent下的核心类 Executor:具有runnable任务的执行者 ExecutorService:一个线程池管理者,实现类有多种,能把runnable,callable提交到线程池中 ...

  6. 【初识】-JUC·Executor框架

    前言 多线程和并发这两个东西真的是向往已久,总是有一种神秘的感觉,想去探索一波,又担心水平不够无法驾驭.想以读书笔记的方式来写,但是又觉得缺少自己的一些思考:但是在没有足够并发编程经验的情况下又没法去 ...

  7. 线程不安全 静态变量_ArrayList线程不安全,JUC是如何处理的

    当我们并发往ArrayList中添加值的时候,当并发过大的时候会出现ConcurrentModificationException(并发修改异常), List list =new ArrayList& ...

  8. 多线程总结-JUC中常用的工具类

    本文只记录JUC中较常用到的一些工具类,只是列举其常见的使用方法,至于其实现原理,此处不做说明. CountDownLatch 一个同步工具类,允许一个或多个线程一直等待,直到其他线程运行完成后再执行 ...

  9. JAVA并发编程JUC基础学习(简介)

    2019独角兽企业重金招聘Python工程师标准>>> 之前写过一篇并发编程的简单实例应用,Future快速实现并发编程,可以很快的在自己的项目中应用,但并不系统,之前说过总结一篇( ...

  10. JUC包中的分而治之策略-为提高性能而生

    一.前言 本次分享我们来共同探讨JUC包中一些有意思的类,包含AtomicLong & LongAdder,ThreadLocalRandom原理. 二.AtomicLong & Lo ...

最新文章

  1. 如何全面认识联邦学习
  2. syslog服务器默认使用协议,什么是syslog协议?
  3. 原子智库 | 刘伟:人工智能快追上人类思维?答案可能让你失望
  4. 数据中台推荐系统入门(二):两种经典的推荐算法
  5. 力扣- - 最短回文串(KMP算法)
  6. 【CSS3】CSS——链接
  7. Java爬取并下载酷狗音乐
  8. 计算机进管理提示找不到入口,win10系统开机提示xxxdll模块已加载但找不到入口点的教程...
  9. linux性能分析工具集(图示)
  10. 使用qsort()和bsearch()函数对值和字符串进行排序和查找
  11. “霸榜CLUE” ,刚刚发布的业界最大中文NLP预训练模型有多强?
  12. Kettle文件下载
  13. 计算机考研408每日一题 day19
  14. 努比亚(nubia) Z18 mini NX611J 解锁BootLoader 并刷入recovery ROOT
  15. Android UI自动化工具-SoloPi
  16. 设计模式六大原则——单一职责原则(SRP)
  17. 简单说 用CSS做一个魔方旋转的效果
  18. 如何参与Github开源社区开发
  19. 【OpenCV 例程300篇】208. Photoshop 对比度自动调整算法
  20. DHU 数据科学技术与应用【8】 第1-4次单元测验 答案

热门文章

  1. 逆袭高富帅 屌丝成长必备APP大搜罗
  2. SpringBoot+Vue实现前后端分离的在线考试报名系统
  3. aix oracle扩容裸lv,aix下扩展lv注意事项
  4. ubuntu从19.04升级到20.04后五笔一直不能输入中文,解决方法
  5. 《权力的游戏》剧情解析全靠它!
  6. hadoop中使用hprof工具进行性能分析
  7. Java虚拟机内存模型简单介绍
  8. flash 转换成html5,文档 - FlashReturn.com,转换flash到支持html5的网页上 - 闪归
  9. 人工智能:风口之上泡沫之中谨慎入坑
  10. mt2503 在MMI版本实现AT+CPBF