一 . 简介AQS

AQS简介

  • 在同步组件的实现中,AQS是核心部分,同步组件的实现者,通过使用AQS提供的模板方法 实现同步组件语义
  • AQS实现了对同步状态的管理以及阻塞线程进行排队,等待通知等等一系列底层的实现处理
  • AQS核心:使用Node实现同步队列,底层是个双向链表,可以用于同步锁或者其他同步装的基础框架

AbstractQueuedSynchronized,虽然类名开头是Abstract,但是他不是抽象类,意义就是说,单独使用它是没有意义的,依赖他去实现同步组件才有意义--相当于没模板方法模式

  • 子类通过继承并实现他的方法,管理其状态 acquire和release
  • 可以同时实现排它锁和共享锁,站在使用者的角度看,它可以帮我们完成两件事,独占控制和共享控制,它的所有子类中要么实现重写了它独占功能的API,要么使用的是共享功能的API,而不会同时使用两套API,即使是他最有名的实现类ReentrantLock,也是通过两个内部类,分别使用者两套API

AQS实现的大致思路,它内部有一个双向的链表,链表的每一个节点都是一个Node的结构,线程会来尝试的获取锁,如果失败了那么它就将当前线程包装成一个Node节点,加入到同步队列中,前一个节点释放锁后,唤醒自己的后继节点,它实现的依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器

此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础

以下类都是依赖AQS是实现的:

  • ReentrantLock
  • ReentrantReadWritrLock.ReadLock
  • ReentrantReadWriteLock.WriteLock

二 . 使用AQS实现自己的锁

自己的锁肯定要去实现lock接口,重写里面的方法,怎么重写呢?使用AQS,将AQS作为内部的帮助器类,重写里面的tryAcquir和tryRelease方法,因为lock()我们采用帮助器acquire的方法实现,而此方法会至少调用一次tryAcquire,同理,释放锁,我们重写帮助器的tryRelease,,,,我们只是简单的使用一下,完成加锁,释放锁,锁重入即可

他面临着两个问题

  1. 怎么知道来拿锁的线程是上一个拿到锁的线程

    • 判断if(当前线程==持有锁的线程){state++;}要求计数器自增
  2. 怎么释放掉锁

    • 重复n次拿到了锁,要求计数器依次减下去

  1. public class AQSDemo01 implements Lock {
  2. private Helper helper = new Helper();
  3. private class Helper extends AbstractQueuedSynchronizer {
  4. @Override
  5. protected boolean tryAcquire(int arg) {
  6. int state = getState();
  7. Thread t = Thread.currentThread();
  8. if (state == 0) {
  9. //如果当前状态的值等于预期的值,就把 当前状态的中修改成 arg的值...... ( 刚才掉坑了, compareAndSerState方法,会帮助我们去对比 手动输进去的0 和 当前的状态)
  10. if (compareAndSetState(0, arg)) {
  11. System.out.println("线程来了"+Thread.currentThread().getName()+" arg=="+arg);
  12. setExclusiveOwnerThread(t);
  13. return true;
  14. }
  15. } else if (getExclusiveOwnerThread() == t) {
  16. setState(state + 1);
  17. return true;
  18. }
  19. return false;
  20. }
  21. @Override
  22. protected boolean tryRelease(int arg) {
  23. if (Thread.currentThread() != getExclusiveOwnerThread()) {
  24. throw new RuntimeException();
  25. }
  26. int state = getState() - arg;
  27. boolean flag = false;
  28. if (state == 0) {
  29. setExclusiveOwnerThread(null);
  30. flag = true;
  31. }
  32. setState(state);
  33. return flag;
  34. }
  35. Condition newCondition() {
  36. return new ConditionObject();
  37. }
  38. }
  39. @Override
  40. public void lock() {
  41. helper.acquire(1);
  42. }
  43. @Override
  44. public void lockInterruptibly() throws InterruptedException {
  45. helper.acquireInterruptibly(1);
  46. }
  47. @Override
  48. public boolean tryLock() {
  49. return helper.tryAcquire(1);
  50. }
  51. @Override
  52. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  53. return helper.tryAcquireNanos(1, unit.toNanos(time));
  54. }
  55. @Override
  56. public void unlock() {
  57. helper.release(1);
  58. }
  59. @Override
  60. public Condition newCondition() {
  61. return helper.newCondition();
  62. }
  63. }

测试类如下,锁正常

  1. public class textAQS {
  2. private int value=0;
  3. private AQSDemo01 lock = new AQSDemo01();
  4. public int next() {
  5. lock.lock();
  6. try {
  7. Thread.sleep(300);
  8. return value++;
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. throw new RuntimeException();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16. /*
  17. * 经典的验证 锁的重复问题 ,在单一的线程下, a() 想在 未释放锁 的前提下 调用b(),前提就是可冲入锁
  18. * */
  19. public void a() {
  20. lock.lock();
  21. System.out.println("a");
  22. b();
  23. lock.unlock();
  24. }
  25. public void b() {
  26. lock.lock();
  27. System.out.println("b");
  28. lock.unlock();
  29. }
  30. public static void main(String[] args) {
  31. textAQS m = new textAQS();
  32. //测试可重入
  33. new Thread(new Runnable() {
  34. @Override
  35. public void run() {
  36. m.a();
  37. }
  38. }).start();
  39. System.out.println("主线程=="+Thread.currentThread().getName());
  40. ExecutorService executorService = Executors.newCachedThreadPool();
  41. //从线程池拿出四条线程执行next任务,查看结果是否同步,同步
  42. for (int i=0;i<4;i++){
  43. executorService.execute(new Runnable() {
  44. @Override
  45. public void run() {
  46. while(true)
  47. System.out.println(Thread.currentThread().getName()+" "+m.next());
  48. }
  49. });
  50. }
  51. }
  52. }

AQS的同步组件

1. CountDownLatch

  • 用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,

    所以在当前计数到达零之前,await 方法会一直受阻塞。当调用了一定次数的CountDown()是计数器的值为零后,会释放所有等待的线程

    ,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置
  • 它的典型使用场景就是分布计算
  • 打个比方

课代表等所有同学交完作业再交给老师,

1: 课代表等待所有的同学(线程)交作业 CountDownLatch cdl = new CountDownLatch(int 学生数)

2: 单个学生交完作用, cdl.countDown() --> 学生数减一

3: 主线程: cdl.await() 只要学生数不为零, 就等待

  1. public class countDownLatch02 {
  2. private static int []nums;
  3. public countDownLatch02(int line){
  4. nums=new int[line];
  5. }
  6. //分隔字符串数组,完成 当前行 ( 一行 ) 相加
  7. public void colculate(String s ,int index,CountDownLatch count){
  8. System.out.println("单行线程开始执行.. "+Thread.currentThread().getName());
  9. String[] s1 = s.split(",");
  10. int total=0;
  11. for (String s2:s1) {
  12. int i = Integer.parseInt(s2);
  13. total+=i;
  14. }
  15. nums[index]=total;
  16. System.out.println(Thread.currentThread().getName() +"线程 计算结果是=="+total);
  17. count.countDown();
  18. }
  19. //分别计算没行的总值
  20. public void sum(){
  21. System.out.println("加总线程开始执行...");
  22. int total =0;
  23. for(int i=0;i<nums.length;i++){
  24. total+=nums[i];
  25. }
  26. System.out.println("执行的结果是=="+total);
  27. }
  28. public static void main(String[] args) throws IOException, InterruptedException {
  29. // 根据行数,
  30. List<String> contents = readFile();
  31. int size= contents.size();
  32. CountDownLatch c = new CountDownLatch(size);
  33. countDownLatch02 latch = new countDownLatch02(size);
  34. System.out.println("zhuxianc");
  35. // //创建出相应数目的线程,
  36. for(int i=0;i<size;i++){
  37. final int j =i;
  38. new Thread(new Runnable() {
  39. @Override
  40. public void run() {
  41. latch.colculate(contents.get(j),j,c);
  42. }
  43. }).start();
  44. }
  45. //在主线程中加总
  46. // System.out.println("当前活跃的实现数"+Thread.activeCount());
  47. /* while((Thread.activeCount())>2){ //自旋,等待其他线程执行完..
  48. System.out.println("当前活跃的实现数"+Thread.activeCount());
  49. }*/
  50. c.await();
  51. latch.sum();
  52. }
  53. /*
  54. * 读取文件,将每一行存放进list数组...
  55. * */
  56. public static List<String> readFile() throws IOException {
  57. List<String> list = new ArrayList<>();
  58. String line=null;
  59. BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\SETextMaven\\textcountDownLatch.txt"));
  60. while ((line = bufferedReader.readLine())!=null){
  61. list.add(line);
  62. }
  63. return list;
  64. }
  65. }

2. Semaphore

  • 常常作用于仅能提供有限访问的资源,比如项目中使用到的数据库的连接数,可能最大只有20,但是外界的并发量却很庞大,所以我们可以使用Semaphore进行控制,当它把并发数控制到1时,和单线程很相似
  • 他可以很容易的控制同一时刻,并发访问某一个资源被的线程数,使用起来也很简单,对需要进行并发控制的代码用 semaphore.acquire()和 semaphore.release(); 包裹起来即可
  1. /*
  2. * 字面意思: 信号量 --> Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目
  3. * 作用: 用来控制同时访问某些特定资源的线程数量,协调各个线程合理使用公共资源
  4. * 简介: Semaphore 可以用来维护当前访问自身的线程个数,并提供了同步机制,比如实现一个文件的允许的并发数
  5. * 应用场景:
  6. * 开启 30条线程 把 一万个文件的内容读取到内存
  7. * 使用Semaphore允许10个线程可以并发执行,将内存中的数据写回数据库
  8. *
  9. * 模拟高并发
  10. * */
  11. public class semaphore {
  12. public static void main(String[] args) {
  13. final int tNum =30;
  14. // ExecutorService executorService;
  15. // executorService = new Executors.newFixedThreadPool();
  16. Semaphore semaphore = new Semaphore(5);//允许一次性允许 并发执行的线程数
  17. for (int i=0;i<100;i++){
  18. new Thread(()->{
  19. try {
  20. semaphore.acquire(); //当前线程获取 Semaphore 的 许可证
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. System.out.println(Thread.currentThread().getName()+"开始任务");
  25. try {
  26. Thread.sleep(1000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. semaphore.release(); // 归还许可证
  31. }).start();
  32. }
  33. }
  34. }

3 . CyclicBarrier

  • 和CountDownLanch不同的是,它描述的是所有的线程相互等待的过程

    构造方法,参数为parties - 在启动 barrier 前必须调用 await() 的线程数
  • 注意点,如果 传入的参数为6,而所有线程一共才五条,那么主线程和 子线程,将永远处于等待状态,因为没有第六条线程执行 await方法... 或者, 线程在执行await()之前,出现异常, 屏障永远不会被满足

实例代码:


  1. /*
  2. * 它允许一组线程相互等待,直到达到某个公共的屏障点,
  3. * 也就是说,所有的线程必须相互等待,--> 直到所有线程都 满足屏障的要求--> 执行后面的任务
  4. *
  5. * 开会:
  6. * await() 屏障 后续的任务
  7. * 公司里的所有人都去开会,--> 先到的人等待迟到的人--> 人到齐了,开会....
  8. * */
  9. /*
  10. * 应用场景,多线程计算数据,最后合并计算结果
  11. * */
  12. /*
  13. * 模拟开会...
  14. * */
  15. import java.util.concurrent.BrokenBarrierException;
  16. import java.util.concurrent.CyclicBarrier;
  17. public class CyclicBarrier01 {
  18. public void meeting(CyclicBarrier cyclicBarrier){
  19. System.out.println(Thread.currentThread().getName()+"到达会议室...");
  20. try {
  21. cyclicBarrier.await(); //此线程等待..
  22. System.out.println(Thread.currentThread().getName()+"准备开会..");
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. } catch (BrokenBarrierException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. public static void main(String[] args) {
  30. CyclicBarrier01 c = new CyclicBarrier01();
  31. // 构造函数1:
  32. // 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,
  33. // 但它不会在启动 barrier 时执行预定义的操作。
  34. //参数:
  35. //parties - 在启动 barrier 前必须调用 await() 的线程数
  36. //抛出:
  37. //IllegalArgumentException - 如果 parties 小于 1
  38. //注意点:
  39. // 如果 传入的参数为6,而所有线程一共才五条,那么主线程和 子线程,将永远处于等待状态,因为没有第六条线程执行 await方法...
  40. // 或者, 线程在执行await()之前,出现异常, 屏障永远不会被满足
  41. CyclicBarrier cyclicBarrier = new CyclicBarrier(6);
  42. for (int i =0;i<5;i++) { //开启五条线程...
  43. new Thread(new Runnable() {
  44. @Override
  45. public void run() {
  46. c.meeting(cyclicBarrier);
  47. }
  48. }).start();
  49. }
  50. // 主线程
  51. try {
  52. cyclicBarrier.await();
  53. } catch (InterruptedException e) {
  54. e.printStackTrace();
  55. } catch (BrokenBarrierException e) {
  56. e.printStackTrace();
  57. }
  58. System.out.println("人都到齐了,开会...");
  59. // cyclicBarrier.reset();
  60. }
  61. }

带 Runable, 当所有预期的线程都await后,先执行Runable里面的任务

  1. public class CyclicBarrier02 {
  2. public void meeting(CyclicBarrier cyclicBarrier){
  3. System.out.println(Thread.currentThread().getName()+"到达会议室...");
  4. try {
  5. cyclicBarrier.await(); //此线程等待..
  6. System.out.println(Thread.currentThread().getName()+"听领导讲话...");
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. } catch (BrokenBarrierException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. public static void main(String[] args) {
  14. CyclicBarrier02 c = new CyclicBarrier02();
  15. // 构造函数2:
  16. /*
  17. 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,
  18. 并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。
  19. 参数:
  20. parties - 在启动 barrier 前必须调用 await() 的线程数
  21. barrierAction - 在启动 barrier 时执行的命令;如果不执行任何操作,则该参数为 null
  22. 抛出:
  23. */
  24. CyclicBarrier cyclicBarrier = new CyclicBarrier(6, new Runnable() {
  25. @Override
  26. public void run() {
  27. System.out.println("开始开会...");
  28. }
  29. });
  30. for (int i =0;i<5;i++) { //开启五条线程...
  31. new Thread(new Runnable() {
  32. @Override
  33. public void run() {
  34. c.meeting(cyclicBarrier);
  35. }
  36. }).start();
  37. }
  38. // 主线程
  39. try {
  40. cyclicBarrier.await();
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. } catch (BrokenBarrierException e) {
  44. e.printStackTrace();
  45. }
  46. System.out.println("主线程也 await了 ... 人都到齐了,开会...");
  47. cyclicBarrier.reset();
  48. }
  49. }

4. J.U.C同步组件 FutrueTask

在 多线程二 基本技能中有详细的将讲解,使用

5 J.U.C 同步组件Fork/Join框架

  • ForkJoin是java7提供的并行执行任务的框架
  • 它的设计思路是把一个大人物Fork成若干个小任务分布计算,然后Join这些子任务的结果,最终得到这个大任务的结果,使用工作窃取算法也就是某个线程从其他的线程的工作队列(双端队列,来窃取的线程从这个队列的尾部取任务,减少竞争)里面窃取任务执行

局限性:

  • 假如说双端任务队列里面就一个任务,那么肯定就出现竞争,而且还有多开辟线程的开销
  • 只能使用Fork和Join去同步,如果使用了别的同步机制.那么同步线程就不能去窃取执行其他任务
  • 工作队列里面的任务不应该是IO操作
  • 任务不能抛出检查异常,它必须通过必要的代码去处理他们

多线程七 AQS的更多相关文章

  1. 多线程(七)JDK原生线程池

    如同数据库连接一样,线程的创建.切换和销毁同样会耗费大量的系统资源.为了复用创建好的线程,减少频繁创建线程的次数,提高线程利用率可以引用线程池技术.使用线程池的优势有如下几点:        1.保持 ...

  2. Java多线程系列--AQS之 LockSupport

    concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS(JAVA CAS原理.unsafe.AQS)框架借助于两个类: Unsafe(提供CAS操作 ...

  3. Java多线程:AQS

    在Java多线程:线程间通信之Lock中我们提到了ReentrantLock是API级别的实现,但是没有说明其具体实现原理.实际上,ReentrantLock的底层实现使用了AQS(AbstractQ ...

  4. java架构之路(多线程)AQS之ReetrantLock显示锁的使用和底层源码解读

    说完了我们的synchronized,这次我们来说说我们的显示锁ReetrantLock. 上期回顾: 上次博客我们主要说了锁的分类,synchronized的使用,和synchronized隐式锁的 ...

  5. Java多线程——<七>多线程的异常捕捉

    一.概述 为什么要单独讲多线程的异常捕捉呢?先看个例子: public class ThreadException implements Runnable{ @Override public void ...

  6. java多线程(七)-线程之间的 协作

    对于多线程之间的共享受限资源,我们是通过锁(互斥)的方式来进行保护的,从而避免发生受限资源被多个线程同时访问的问题.那么线程之间既然有互斥,那么也会有协作.线程之间的协作也是必不可少的,比如 盖个商场 ...

  7. C# 多线程七之Parallel

    1.简介 关于Parallel不想说太多,因为它是Task的语法糖,至少我是这么理解的,官方文档也是这么说的,它本身就是基本Task的.假设我们有一个集合,不管是什么集合,我们要遍历它,首先想到的是F ...

  8. 并发和多线程(七)--volatile

    volatile: 相当于轻量级的synchronized,只能用来修饰变量,线程安全的三个特性通过volatile能实现其中的两个 原子性: 在之前的文章有说到,通过Atomic相关类.synchr ...

  9. Java多线程——<八>多线程其他概念

    一.概述 到第八节,就把多线程基本的概念都说完了.把前面的所有文章加连接在此: Java多线程——<一>概述.定义任务 Java多线程——<二>将任务交给线程,线程声明及启动 ...

随机推荐

  1. cygwin报错 /bin/bash: Operation not permitted

    如题,使用Cygwin过程中本来好好的,突然就不能登录了,每个用户登录都报错 /bin/bash: Operation not permitted.开始也以为是没有权限之类的,重装弄了很久也不行.后面 ...

  2. 简单学习【1】——使用webpack

    使用webpack webpack命令 webpack配置 第三方脚手架 1.webpack命令 webpack - h (webpack 所有的选项) webpack -v (查看webpack的版 ...

  3. 图片验证码推导逻辑,Image.new,ImageDraw, ImageFont.truetype的用法

    #一, 创建图片并在图上添加文本 from PIL import Image,ImageDraw,ImageFont a = '我们不一样' # 定义文本 font = ImageFont.truet ...

  4. 表达式和运算符知识总结(js)

    文章目录: 一. 表达式和语句的区别 二. 自增自减运算符的运算规则 一. 表达式和语句的区别 表达式(expression)是JavaScript中的一个短语,JavaScript解释器会将其计算( ...

  5. python操作文件和目录查看、创建、删除、复制

    python内置了os模块可以直接调用操作系统提供的接口函数,os.name查询的是操作系统,‘nt’表示windows系统 >>> import os >>> o ...

  6. Tomcat系列(二)- EndPoint源码解析

    在上一节中我们描述了Tomcat的整体架构, 我们知道了Tomcat分为两个大组件,一个连接器和一个容器. 而我们这次要讲的 EndPoint的组件就是属于连接器里面的. 它是一个通信的端点,就是负责 ...

  7. Android 项目优化(五):应用启动优化

    介绍了前面的优化的方案后,这里我们在针对应用的启动优化做一下讲解和说明. 一.App启动概述 一个应用App的启动速度能够影响用户的首次体验,启动速度较慢(感官上)的应用可能导致用户再次开启App的意 ...

  8. Aery的UE4 C++游戏开发之旅(1)基础对象模型

    目录 UObject Actor种类 AActor APawn(可操控单位) AController(控制器) AGameMode(游戏模式) AHUD(HUD) ... Component种类 UA ...

  9. VS2019 开发Django(三)------连接MySQL

    导航:VS2019开发Django系列 下班回到家,洗漱完毕,夜已深.关于Django这个系列的博文,我心中的想法就是承接之前的微信小程序的内容,做一个服务端的管理中心,上新菜单,调整价格啊!之类的, ...

  10. 网络爬虫引发的问题及robots协议

    一.网络爬虫的尺寸 1.以爬取网页,玩转网页为目的进行小规模,数据量小对爬取速度不敏感的可以使用request库实现功能(占90%) 2.以爬取网站或爬取系列网站为目的,比如说获取一个或多个旅游网站的 ...