1. AQS共享模式

  前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码。

  首先还是从顶级接口acquireShared()方法入手:

  1. public final void acquireShared(int arg) {
  2. if (tryAcquireShared(arg) < 0)
  3. doAcquireShared(arg);
  4. }

  与acquire()方法一样,tryAcquireShared()为自己是实现的对资源获取的接口,AQS对返回值的语义已经定义好了,小于0表示失败,0表示成功,但是没有剩余资源,大于0表示成功,且还有剩余资源,其他线程还可以去获取,所以这里的流程就是,先调用tryAcquireShared();当不能获取资源时,调用doAcquireShared()方法让线程进入等待队列。

  doAcquireShared(int)方法,该方法用于将当前线程放入到等待队列中等待,直到其他线程唤醒并成功获取到资源才开始执行。源码如下:

  1. private void doAcquireShared(int arg) {
  2. final Node node = addWaiter(Node.SHARED);
  3. boolean failed = true;
  4. try {
  5. boolean interrupted = false;
  6. for (;;) {
  7. final Node p = node.predecessor();
  8. if (p == head) {
  9. int r = tryAcquireShared(arg);
  10. if (r >= 0) {
  11. setHeadAndPropagate(node, r);
  12. p.next = null; // help GC
  13. if (interrupted)
  14. selfInterrupt();
  15. failed = false;
  16. return;
  17. }
  18. }
  19. if (shouldParkAfterFailedAcquire(p, node) &&
  20. parkAndCheckInterrupt())
  21. interrupted = true;
  22. }
  23. } finally {
  24. if (failed)
  25. cancelAcquire(node);
  26. }

  将当前线程加入到等待队列队尾,并返回当前线程所在的节点,标记是否成功,判断是否被中断,获取当前节点的前驱,如果前驱不为空,如果前驱等于头节点,则表示当前线程被唤醒,因为头节点是持有资源的线程,当前节点可能会被头节点唤醒,尝试去获取资源,r>=0,表示获取成功,将当前节点设置为头节点,如果还有资源可以尝试唤醒下一个等待线程。判断是够被中断过,如果中断过,则清除中断标记,shouldParkAfterFailedAcquire()判断线程状态是否可以等待并找一个能够被唤醒的点进入等待,等着被unpark()或interrupt(),parkAndCheckInterrupt()使线程被waiting。

  跟独占模式相比,这里是将selfInterrupt()放到了doAcquireShared()中,具体为啥我也不知道,但是这里的问题是,当第一个线程执行完之后释放资源,可能释放的资源只有3个,但是当前线程需要4个,而后面一个线程只需要2个,再后一个线程只需要1个,这种情况下,当前线程也是不会去唤醒后两个线程的,它会继续等待着其他的线程释放资源,独享模式下这样没问题,但是在共享模式下,多个线程可以同时执行,这样的策略会使得后面的两个线程会因为没被唤醒而没法执行,其实也算是问题,这里是cas严格保证了入队顺序和出对顺序,降低了并发,但是却是保证了安全的。

  setHeadAndPropagate(Node, int)是将当前线程设置为头节点,当资源还有剩余的情况下去唤醒其他资源。

  1. private void setHeadAndPropagate(Node node, int propagate) {
  2. Node h = head; // Record old head for check below
  3. setHead(node);
  4. /*
  5. * Try to signal next queued node if:
  6. * Propagation was indicated by caller,
  7. * or was recorded (as h.waitStatus either before
  8. * or after setHead) by a previous operation
  9. * (note: this uses sign-check of waitStatus because
  10. * PROPAGATE status may transition to SIGNAL.)
  11. * and
  12. * The next node is waiting in shared mode,
  13. * or we don't know, because it appears null
  14. *
  15. * The conservatism in both of these checks may cause
  16. * unnecessary wake-ups, but only when there are multiple
  17. * racing acquires/releases, so most need signals now or soon
  18. * anyway.
  19. */
  20. if (propagate > 0 || h == null || h.waitStatus < 0 ||
  21. (h = head) == null || h.waitStatus < 0) {
  22. Node s = node.next;
  23. if (s == null || s.isShared())
  24. doReleaseShared();
  25. }
  26. }

  setHead将当前节点设置为头节点,当资源还有剩余的情况下,唤醒当前节点的相邻节点。

  共享模式的流程就是尝试获取资源,获取资源失败,则进入等待,与独享模式相比,共享只是多了在资源剩余的情况下去唤醒其他线程的操作而已。

  releaseShared()共享模式下释放共享资源的顶级入口,释放指定量的资源,如果成功释放且允许唤醒其他线程来获取资源,则它会唤醒队列里的其他等待线程来获取资源,源码:

  1. public final boolean releaseShared(int arg) {
  2. if (tryReleaseShared(arg)) {
  3. doReleaseShared();
  4. return true;
  5. }
  6. return false;
  7. }

  调用tryReleaseShared()尝试释放资源,这里的tryReleaseShared()也是自己实现的,成功,则调用doReleaseShared()唤醒后继节点,

  doReleaseShared()用于唤醒后继节点

  源码:

  1. private void doReleaseShared() {
  2. /*
  3. * Ensure that a release propagates, even if there are other
  4. * in-progress acquires/releases. This proceeds in the usual
  5. * way of trying to unparkSuccessor of head if it needs
  6. * signal. But if it does not, status is set to PROPAGATE to
  7. * ensure that upon release, propagation continues.
  8. * Additionally, we must loop in case a new node is added
  9. * while we are doing this. Also, unlike other uses of
  10. * unparkSuccessor, we need to know if CAS to reset status
  11. * fails, if so rechecking.
  12. */
  13. for (;;) {
  14. Node h = head;
  15. if (h != null && h != tail) {
  16. int ws = h.waitStatus;
  17. if (ws == Node.SIGNAL) {
  18. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  19. continue; // loop to recheck cases
  20. unparkSuccessor(h);
  21. }
  22. else if (ws == 0 &&
  23. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  24. continue; // loop on failed CAS
  25. }
  26. if (h == head) // loop if head changed
  27. break;
  28. }
  29. }

  自旋,通过unparkSuccessor()唤醒后继节点,这样就释放掉了资源。

  以上就是关于AQS共享模式的源码的分析。

2. CountDownLatch的使用及原理

  CountDownLatch是jdk并发包中提供的负责并发编程的类,它也是AQS中共享模式的一种运用,利用它可以实现类似于计数器的功能,比如当以个线程需要等待其他几个线程的结果,但是其他几个线程又需要并行的执行时,就可以利用该类来实现,这里我们以一个求和的程序来举例该类的用法,

  2.1 使用方式

  存在一个文件中有如下数据

  1. 12,13,20,40
  2. 50,60,80,90
  3. 50,23,40
  4. 16,13

  我们需要每一行用一个线程来进行加法计算,当所有线程执行完成后,将所有线程计算结果做一次汇总,实现如下:

  计算每一行之和的代码:

  1. public void calc (String line, int index, CountDownLatch countDownLatch) {
  2. String[] nus = line.split(",");
  3. int total = 0;
  4. for (String n : nus) {
  5. total += Integer.parseInt(n);
  6. }
  7. nums[index] = total;
  8. System.out.println(Thread.currentThread().getName() + " 执行计划任务..." + line + " 结果为:" + total);
  9. countDownLatch.countDown();
  10. }

  每一个线程执行计算完毕后都会调用countDownLatch.countDown();使得当前运行线程减一

计算总和的代码:

  1. public void sum () {
  2. System.out.println(Thread.currentThread().getName() + "汇总线程开始执行...");
  3. int total = 0;
  4. for (int i = 0; i < nums.length; i++) {
  5. total += nums[i];
  6. }
  7. System.out.println("总结果为:" + total);
  8. }

  计算每一行之和的调用方式和计算总和的调用方式:

  1. package com.wangx.thread.t7;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.File;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileReader;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. import java.util.concurrent.CountDownLatch;
  10.  
  11. public class AddMain {
  12.  
  13. public static void main(String[] args) {
  14. //读取文件
  15. final List<String> contents = readFile();
  16. //初始化countDownLatch 有几个线程执行构造参数就传几个
  17. final CountDownLatch countDownLatch = new CountDownLatch(contents.size());
  18. int lineNum = contents.size();
  19. final AddNumber addNumber = new AddNumber(lineNum);
  20. //多个线程同时执行
  21. for (int i = 0; i < lineNum; i++) {
  22. final int k = i;
  23. new Thread(new Runnable() {
  24. @Override
  25. public void run() {
  26. addNumber.calc(contents.get(k), k, countDownLatch);
  27. }
  28. }).start();
  29. }
  30.  
  31. //等待着直到所有线程执行完之后执行下面的代码
  32. try {
  33. countDownLatch.await();
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. //执行汇总
  38. addNumber.sum();
  39. }
  40.  
  41. private static List<String> readFile() {
  42. List<String> contents = new ArrayList<>();
  43. String line = null;
  44. try {
  45. BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\JavaDev\\spring\\thread\\src\\com\\wangx\\thread\\t7\\numers.txt"));
  46. while ((line = bufferedReader.readLine()) != null) {
  47. contents.add(line);
  48. }
  49. } catch (Exception e) {
  50. e.printStackTrace();
  51. }
  52. return contents;
  53. }
  54. }

  先初始化CountDownLatch实例,构造参数为需要并行执行的线程个数,调用await()方法等待,知道所有并行执行的线程执行完成。

  2.2 实现原理(源码分析)

   首先看初始化的构造方法:

  1. public CountDownLatch(int count) {
  2. if (count < 0) throw new IllegalArgumentException("count < 0");
  3. this.sync = new Sync(count);
  4. }

  它实例化了一个内部同步器Sync,我们继续看Sync的构造:

  1. Sync(int count) {
  2. setState(count);
  3. }

  这里调用了AQS的方法,直接设置了AQS的状态,达到了初始化多少个资源的目的,因为AQS是提供一个原子的int类型state来维护状态的,我们的示例中初始化了contents.size()个资源,接下来看countDown()方法,

  1. public void countDown() {
  2. sync.releaseShared(1);
  3. }

  调用releaseShared()来释放资源,这是AQS原理的运用,我们这里主要看在CountDownLatch中tryReleaseShared()方法的实现:

  

  1. protected boolean tryReleaseShared(int releases) {
  2. // Decrement count; signal when transition to zero
  3. for (;;) {
  4. int c = getState();
  5. if (c == 0)
  6. return false;
  7. int nextc = c-1;
  8. if (compareAndSetState(c, nextc))
  9. return nextc == 0;
  10. }
  11. }
  12. }

  实现也很简单,状态为0,资源已经被释放,自旋,更改状态,示例当contents个线程调用countDown完毕之后nextc == 0;才会成立,此时的releaseShared()才回去唤醒其他的等待的线程,示例中是主线程在调用求和的方法。

  await()方法:

  1. public void await() throws InterruptedException {
  2. sync.acquireSharedInterruptibly(1);
  3. }

  同样是调用内部同步器的方法来执行,

  acquireSharedInterruptibly():

  1. public final void acquireSharedInterruptibly(int arg)
  2. throws InterruptedException {
  3. if (Thread.interrupted())
  4. throw new InterruptedException();
  5. if (tryAcquireShared(arg) < 0)
  6. doAcquireSharedInterruptibly(arg);
  7. }

  该方法的功能是获取当前获取资源,如果获取资源失败,则调用doAcquireSharedInterruptibly()将线程放入到等待队列中等待,

  doAcquireSharedInterruptibly()与doAcquireShared()相似,看源码:

  1. private void doAcquireSharedInterruptibly(int arg)
  2. throws InterruptedException {
  3. final Node node = addWaiter(Node.SHARED);
  4. boolean failed = true;
  5. try {
  6. for (;;) {
  7. final Node p = node.predecessor();
  8. if (p == head) {
  9. int r = tryAcquireShared(arg);
  10. if (r >= 0) {
  11. setHeadAndPropagate(node, r);
  12. p.next = null; // help GC
  13. failed = false;
  14. return;
  15. }
  16. }
  17. if (shouldParkAfterFailedAcquire(p, node) &&
  18. parkAndCheckInterrupt())
  19. throw new InterruptedException();
  20. }
  21. } finally {
  22. if (failed)
  23. cancelAcquire(node);
  24. }
  25. }

  两段代码都与前面共享模式时的doAcquireShared()方法功能相同,只是它是一个可中断的实现。

  这就是CountDownLatch的实现原理,先初始化资源状态,每个线程执行完成后将释放资源,更改状态,直到最后一个执行的线程释放完资源,此时状态为0时,尝试去唤醒等待的线程,也就是执行countdown·.await()的线程。

  这里就分享完了AQS的共享模式已经CountDownLatch的使用及原理,限于笔者水平有限,文中错误之处希望各位能够指出,谢谢!

原文 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理的更多相关文章

  1. 并发编程学习笔记(8)----ThreadLocal的使用及源码分析

    1. ThreadLocal的理解 ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境 ...

  2. 并发编程学习笔记(5)----AbstractQueuedSynchronizer(AQS)原理及使用

    (一)什么是AQS? 阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架, ...

  3. 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理

    在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...

  4. 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

  5. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  6. 并发编程学习笔记(15)----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

  7. JUC并发编程学习笔记

    JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...

  8. 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理

    1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...

  9. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

随机推荐

  1. sharepoint 訪问缩略图

    Sharepoint缩略图 简单介绍 Sharepoint2010中有专门的图片库,当你新建图片库后,向图片上传一部分图片.当你浏览这个库时显示一排排小图片.当点击一个图片时进入显示的是大图.不要简单 ...

  2. HDU3367 Pseudoforest 【并查集】+【贪心】

    Pseudoforest Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) To ...

  3. tf.float32 implicity

    简介  |  TensorFlow https://tensorflow.google.cn/programmers_guide/low_level_intro 我们来构建一个简单的计算图.最基本的指 ...

  4. 彻底弄懂px,em和rem的区别

    国内的设计大师都喜欢用px,而国外的网站大都喜欢用em和rem,那么三者有什么区别,又各自有什么优劣呢? px特点: 1.IE无法调整那些使用px作为单位的字体大小: 2.国外大部分网站能够调整的原因 ...

  5. ATM网络

    ATM是Asynchronous Transfer Mode(ATM)异步传输模式的缩写,是实现B-ISDN的业务的核心技术之一.ATM是以信元为基础的一种分组交换和复用技术

  6. flash、flex builder、flash builder、 air的关系

    flash VS flex builder flash被adobe收购的时候是flash8,已经可以AS2面向对象了. 而被adobe收购后,adobe准备把flash打造成一个开发工具.就比如JBU ...

  7. luogu 1726 上白泽惠音

    题目大意: 给一个有向图 求一个最大的强连通分量,输出这个强连通分量里的所有元素 若两个联通分量内点数相同 则输出字典序小的那个 思路: 直接tarjan 对每个连通分量,求一下最小点,然后判断字典序 ...

  8. 使用Oracle Sql Developer将SQL SERVER 2008数据库移植到Oracle 11g

    ORACLE官方提供的Sql Developer自带的Oracle Migration Workbench. 什么是Oracle SQL Developer?在官方页面上,是这样介绍它的: Oracl ...

  9. 重装Eclipse 往其中加Python插件时 遇到不能独立运行c c++ python 代码时修改办法:

    鼠标移动到新建项目处 ,右键->run as-> run configuration->选择Enable auto build 即可.

  10. Spark 决策树--分类模型

    package Spark_MLlib import org.apache.spark.ml.Pipeline import org.apache.spark.ml.classification.{D ...