闭锁(Latch)

闭锁(Latch):一种同 步方法,可以延迟线程的进度直到线程到达某个终点状态 。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都 将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的 ,它确 保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。

CountDownLatch 是JDK 5+里面闭锁的一个实现,允许一个或者多个线程等待某个事件的发生。CountDownLatch 有一个正数计数器,countDown 方法对计数器做减操作,await 方法等待计数器达到0。所有await 的线程都会阻塞直到计数器为0或者等待线程中断或者超时。

CountDownLatch 的API如下。

    * public void await() throws InterruptedException* public boolean await(long timeout, TimeUnit unit) throws InterruptedException* public void countDown()* public long getCount()

其中getCount() 描述的是当前计数,通常用于调试目的。

下面的例子中描述了闭锁的两种常见的用法。

    import java.util.concurrent.CountDownLatch;public class CountDownLatchTest {public static void main(String[] args){final CountDownLatch startLatch = new CountDownLatch(1);final CountDownLatch overLatch = new CountDownLatch(10);for (int i = 0; i < 100; i++) {new Thread(new Runnable() {public void run() {try {startLatch.await();System.out.println(1);} catch (InterruptedException ex) {Thread.currentThread().interrupt();} finally {overLatch.countDown();}}}).start();}//long start = System.nanoTime();startLatch.countDown();try {overLatch.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(2);//return System.nanoTime() - start;}}

在上面的例子中使用了两个闭锁,第一个闭锁确保在所有线程开始执行任务前,所有准备工作都已经完成,一旦准备工作完成了就调用startLatch.countDown() 打 开闭锁,所有线程开始执行。第二个闭锁在于确保所有任务执行完成后主线程才能继续进行,这样保证了主线程等待所有任务线程执行完成后才能得到需要的结果。 在第二个闭锁当中,初始化了一个N次的计数器,每个任务执行完成后都会将计数器减一,所有任务完成后计数器就变为了0,这样主线程闭锁overLatch 拿到此信号后就可以继续往下执行了。

根据前面的happend-before法则 可以知道闭锁有以下特性:

内存一致性效果:线程中调用 countDown() 之前的操作 happen-before 紧跟在从另一个线程中对应 await() 成功返回的操作。

在上面的例子中第二个闭锁相当于把一个任务拆分成N份,每一份独立完成任务,主线程等待所有任务完成后才能继续执行。这个特性在后面的线程池框架中会用到,其实FutureTask 就可以看成一个闭锁。后面的章节还会具体分析FutureTask 的。

同样基于探索精神,仍然需要“窥探”下CountDownLatch 里面到底是如何实现await*countDown 的。

首先,研究下await() 方法。内部直接调用了AQSacquireSharedInterruptibly(1)

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}

前面一直提到的都是独占锁(排它锁、互斥锁),现在就用到了另外一种锁,共享锁。

所谓共享锁是说所有共享锁的线程共享同一个资源,一旦任意一个线程拿到共享资源,那么所有线程就都拥有的同一份资源。也就是通常情况下共享锁只是一个标志,所有线程都等待这个标识是否满足,一旦满足所有线程都被激活(相当于所有线程都拿到锁一样)。这里的闭锁CountDownLatch 就是基于共享锁的实现。

闭锁中关于AQStryAcquireShared 的实现是如下代码(java.util.concurrent.CountDownLatch.Sync.tryAcquireShared ):

public int tryAcquireShared(int acquires) {return getState() == 0? 1 : -1;
}

在这份逻辑中,对于闭锁而言第一次await时tryAcquireShared应该总是-1,因为对于闭锁CountDownLatch 而言state 的值就是初始化的count 值。这也就解释了为什么在countDown 调用之前闭锁的count 总是>0。

private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCreturn;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())break;}} catch (RuntimeException ex) {cancelAcquire(node);throw ex;}// Arrive here only if interruptedcancelAcquire(node);throw new InterruptedException();
}

上面的逻辑展示了如何通过await 将所有线程串联并挂起,直到被唤醒或者条件满足或者被中断。整个过程是这样的:

  1. 将当前线程节点以共享模式加入AQSCLH 队列中(相关概念参考这里 和这里 )。进行2。
  2. 检查当前节点的前任节点,如果是头结点并且当前闭锁计数为0就将当前节点设置为头结点,唤醒继任节点,返回(结束线程阻塞)。否则进行3。
  3. 检查线程是否该阻塞,如果应该就阻塞(park),直到被唤醒(unpark)。重复2。
  4. 如果2、3有异常就抛出异常(结束线程阻塞)。

这里有一点值得说明下,设置头结点并唤醒继任节点setHeadAndPropagate 。由于前面tryAcquireShared 总是返回1或者-1,而进入setHeadAndPropagate 时总是propagate>=0 ,所以这里propagate==1 。后面唤醒继任节点操作就非常熟悉了。

private void setHeadAndPropagate(Node node, int propagate) {setHead(node);if (propagate > 0 && node.waitStatus != 0) {Node s = node.next;if (s == null || s.isShared())unparkSuccessor(node);}
}

从上面的所有逻辑可以看出countDown 应该就是在条件满足(计数为0)时唤醒头结点(时间最长的一个节点),然后头结点就会根据FIFO队列唤醒整个节点列表(如果有的话)。

CountDownLatchcountDown 代码中看到,直接调用的是AQSreleaseShared(1) ,参考前面的知识,这就印证了上面的说法。

tryReleaseShared 中正是采用CAS操作减少计数(每次减-1)。

public boolean tryReleaseShared(int releases) {for (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}
}

整个CountDownLatch 就是这个样子的。其实有了前面原子操作和AQS 的原理及实现,分析CountDownLatch 还是比较容易的

深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)相关推荐

  1. 深入浅出 Java Concurrency (6): 锁机制 part 1[转]

    前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最后的总结篇来整体说明.从这一章开始花少量的篇幅谈谈锁机制. 上一个章节中谈到了锁机制,并且针对于原子操作谈了一些相关的概念和设 ...

  2. [转] 多线程 《深入浅出 Java Concurrency》目录

    http://ifeve.com/java-concurrency-thread-directory/ synchronized使用的内置锁和ReentrantLock这种显式锁在java6以后性能没 ...

  3. 深入浅出 Java Concurrency - 目录 [转]

    这是一份完整的Java 并发整理笔记,记录了我最近几年学习Java并发的一些心得和体会. J.U.C 整体认识 原子操作 part 1 从AtomicInteger开始 原子操作 part 2 数组. ...

  4. 【java线程】锁机制:synchronized、Lock、Condition

    [Java线程]锁机制:synchronized.Lock.Condition 原创 2013年08月14日 17:15:55 标签:Java /多线程 74967 http://www.infoq. ...

  5. 深入浅出MySQL事务处理和锁机制

    深入浅出MySQL事务处理和锁机制 2015-01-13 架构师之旅 1. 事务处理和并发性 1.1. 基础知识和相关概念 1 )全部的表类型都可以使用锁,但是只有 InnoDB 和 BDB 才有内置 ...

  6. Java中的锁机制 -- 乐观锁、悲观锁、自旋锁、可重入锁、读写锁、公平锁、非公平锁、共享锁、独占锁、重量级锁、轻量级锁、偏向锁、分段锁、互斥锁、同步锁、死锁、锁粗化、锁消除

    文章目录 1. Java中的锁机制 1.1 乐观锁 1.2 悲观锁 1.3 自旋锁 1.4 可重入锁(递归锁) 1.5 读写锁 1.6 公平锁 1.7 非公平锁 1.8 共享锁 1.9 独占锁 1.1 ...

  7. 深入浅出 Java Concurrency (33): 线程池 part 6 线程池的实现及原理 (1)[转]

    线程池数据结构与线程构造方法 由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构.图1描述了这种数据结构. 图1 Thre ...

  8. 【MySQL】深入浅出剖析mysql事务锁机制 - 笔记

    事务的基本概念 事务的特点(ACID) 快照:之前的某一个历史的状态(类比虚拟机的快照),用于实现原子性 隔离性:通过锁机制来实现 锁:共享锁,排它锁,独占锁,临键锁,间隙锁,自增锁,意向锁 持久性: ...

  9. JAVA synchronized关键字锁机制(中)

    synchronized 锁机制简单的用法,高效的执行效率使成为解决线程安全的首选. 下面总结其特性以及使用技巧,加深对其理解. 特性: 1. Java语言的关键字,当它用来修饰一个方法或者一个代码块 ...

最新文章

  1. plus rss.php,dedecms织梦rss输出改成全文输出
  2. 《结对-结对编项目作业名称-开发环境搭建过程》
  3. cocosc++怎么打印_Lua调用C++时打印堆栈信息
  4. 2021阿里云“API满意度”调研
  5. 大数据学习(10)--流计算
  6. ubuntu14.04上网问题
  7. PHP创建圆柱体的类,创建一个类
  8. crf模型 java_定制你自己的CRF模型
  9. java 串口判断报文完整_如何判断串口接收完成一帧数据
  10. logistic公式形式的由来,从广义线性回归说起
  11. [转]magento2项目上线注意事项 切换到产品模式
  12. request.getInputStream中文乱码解决方案
  13. IDEA导入eclipse项目并部署到tomcat
  14. 连麦互动技术及其连麦调研
  15. 冰点文库下载器去广告版百度文库下载工具
  16. PHP 下载保存文件到本地
  17. Python——计算程序运行帧率(FPS)
  18. 世界上第一台通用计算机是多少年诞生的,世界上第一台通用计算机ENIAC是( )年诞生的。...
  19. 【OpenCV 例程 300篇】249. 特征描述之视网膜算法(FREAK)
  20. PMBOK(第五版)学习笔记 —— ITTO(输入、工具与技术及输出)汇总

热门文章

  1. 如何两台电脑共享文件?
  2. WORDPRESS QQ扫码登录插件
  3. FIPS 140-3与140-2的差异-5
  4. 实验2:天气查询小程序
  5. 搜狗输入法如何输入直角引号(「『』」 )
  6. python k线斜率计算公式_python – 计算Numpy(或Scipy)中的斜率
  7. sed命令定义和常用方式
  8. 已使用管理员权限运行CMD,仍报错OSError: [WinError 5] 拒绝访问。: ‘E:\\Code\\Python\\Git\\stable-diffusion-webui\\venv\\
  9. amazon mechanical turk介绍
  10. ORB-SLAM——a Versatile and Accurate Monocular SLAM System)