AQS的应用

文章目录

  • 1. 概述
  • 2. ReentrantLock 原理
    • 2.1 非公平锁实现原理
    • 2.2 可重入原理
    • 2.3 可打断原理
    • 2.4 条件变量实现原理
  • 3. 读写锁原理
  • 4. 信号量 Semaphore
  • 5. CountdownLatch

什么是AQS
口述:全称是 AbstractQueuedSynchronizer,是一个框架,提供了这种 通用的同步器机制,它里面也是定义了很多的方法,像获取锁啊释放锁啊,其实释放啊获取啊是基于state属性来做的,state属性呢表示了现在加没加锁,是需要子类去定义和维护这个状态的,控制如何获取锁和释放锁,对state状态的修改也是用到了CAS机制,保证了修改的正确性,其实AQS里面还定义了很多其他的方法,他呢是一个通用的框架,像ReentrantLock、Countdownlatch、信号量啊都是基于AQS实现的。

1. 概述

AQS的应用_第1张图片

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - cas 机制设置 state 状态
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
  • 条件变量来实现等待唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

AQS 要实现的功能目标

  • 阻塞版本获取锁 acquire 非阻塞的版本尝试获取锁 tryAcquire
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制及共享机制
  • 条件不满足时的等待机制

2. ReentrantLock 原理

2.1 非公平锁实现原理

口述:ReentrantLock 里面的加锁啊解锁啊这些其实都是调用AQS里面的相关的方法的,像加锁lock方法,AQS又有不同的实现比如说公平的非公平的,首先它会以CAS方式修改state状态,如果修改成功那就表示加锁成功,将Owner线程设置为当前这个线程,如果加锁失败会进行acquire(1),acquire里面包含try Acquire,此时会让线程再以CAS方式来重试一次,重试了还是没获得锁就会创建一个Node对象然后加入到队列中去,这个队列其实是一个双向链表,并且再将Node加入到队列的这部分代码中,它是一直循环的是一个死循环,它会记录当前节点的前驱节点,自己呢(线程)就park住,也就实现了加锁。解锁呢也是一样,也是调用AQS中的方法,将state设置为0,将owner线程设置为null,然后unpark恢复头节点后面的线程就是唤醒一个嘛,那这个线程就能接着在park住的位置继续执行,它是在一个死循环里面,然后又循环再获取锁,修改state状态修改owner线程。

AQS的应用_第2张图片

2.2 可重入原理

其实就是判断当前线程和owner线程是不是同一个, 是的话就表示还是自己,让state+1,解锁的话也是一样,让state-1

2.3 可打断原理

可打断的话其实就是在park过程中,它会抛出异常退出循环。而不可打断只是标记了一下。

2.4 条件变量实现原理

当调用await时,将这个线程加入到条件变量的等待队列,这时候state状态为-2,并且释放同步器上的锁:owner线程设为null,state设置为0,unparkAQS队列中的下一个节点,调用signal 时,会将条件变量中的这个node加入到AQS队列尾部,并且设置这个node的state为0

3. 读写锁原理

口述
其实和ReentrantLock 加锁啊解锁啊流程是差不多的,只不过它将锁分为了读锁和写锁,就是用state的高16位和低16位来表示当前锁的状态,读锁和读锁是可以同时发生的,读写的这种场景就不行了,比如说先有一个线程加了写锁,那后续的读锁就得进入队列等待,不过这个node节点里面会记录成share共享模式,这个队列中有的node是读锁,也就是share模式,有的是写锁,那么当第一个线程释放了写锁后,会唤醒head的下一个节点,下一个节点会看下下个节点是不是读锁,是的话也会唤醒,直到遇到写锁,这个其实就是读读的话是不会冲突的,读写的话那就不能同时执行了,会产生冲突。这个其实就是读写锁的一个原理吧,其实就是将state分为高16位让读锁用,低16位让写锁用,那如果读读的情况是不会阻塞的,将state这个高16位+1就可以,有多少个读锁就加多少次,那如果读写的情况的话是会阻塞的,得等到前一个将锁释放了吧自己唤醒才能执行。

4. 信号量 Semaphore

用来限制能同时访问共享资源的线程上限,它限制的是线程,而不是限制的资源数,像Tomcat 的LimitLatch 它其实也是扩展了AQS,它限制的就是这种资源数:连接数。

5. CountdownLatch

用来进行线程同步协作,等待所有的线程完成倒计时
awaut()用来等待计数归零,countDown()用来让计数减一

举例:A、B线程都执行完才能执行C线程

  1. 初始化CountdownLatch的大小为2,C这里CountdownLatch的await,A、B执行完了就countDown
  2. join也能实现,C这里A.Join、B.Join,但是join不太好,join必须等AB线程都结束才能让C执行,而 CountdownLatch能更好的配合线程池。

应用:等待多个远程调用结束

  • 使用CountdownLatch(用在没有返回值的情况):创建线程池,每一个远程调用完成执行countDown,主线程await(远程调用数量)等待
  • 使用future(用在有返回值的情况):主线程这里future.get这里等待每个远程调用线程的返回。

你可能感兴趣的:(JUC并发编程,juc)