AQS

AQS概要

  • AQS核心 ,(并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer (AQS)),抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,比如我们之前的课程上讲到的: 的ReentrantLock/Semaphore/CountDownLatchAQS
  • ReentrantLock重入锁                
  • Condition条件判断    
  • ReadWriteLock读写锁                                
  • LockSupport
  • 减少锁的竞争
  • AQS源码解析

AOS定义

  1. 它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,state的访问方式有三种: getState()、 setState() 、compareAndSetState()
  2. AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share (共享,多个线程可同时执行,例如Semaphore/CountDownLatch)不同的自定义同步器争用共享资源的方式也不同。
  3. 自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
  4. isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  5. tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  6. tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  7. tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  8. tryReleaseShared(int):共享方式。尝试释放资源,成功则返回true,失败则返回false
  9. 以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的
  10. 重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果

AQS-ReentrantLock

  • 公平锁和非公平锁:
  • Lock lock = new ReentrantLock(boolean isFair);
  • lock用法:
  • tryLock():尝试获得锁,获得结果用true/false返回。 tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回
  • isFair():是否是公平锁
  • isLocked():是否锁定
  • getHoldCount(): 查询当前线程保持此锁的个数,也就是调用lock()次数
  • lockInterruptibly():优先响应中断的锁
  • getQueueLength():返回正在等待获取此锁定的线程数
  • getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数
  • hasQueuedThread(Thread thread): 查询指定的线程是否正在等待此锁
  • hasQueuedThreads():查询是否有线程正在等待此锁
  • hasWaiters():查询是否有线程正在等待与此锁定有关的condition条件
  • 再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()调用线程,然后主调用线程就会从await()函数返回,继续后余动作

AQS-Condition

  • 还记得我们在使用synchronized的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作
  • 那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition。这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition
  • 我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知

AQS-ReentrantReadWriteLock

  • 读写锁ReentrantReadWriteLock,其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁
  • 之前学synchronized、ReentrantLock时,我们知道,同一时间内,只能有一个线程进行访问被锁定的代码,那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问
  • 口诀:读读共享,写写互斥,读写互斥

AQS-LockSupport

  • LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于”许可(permit)”作为关联,permit相当于一个信号量(0,1),默认是0. 线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态
  • LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦
  • unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序

AQS-锁优化

  • 避免死锁
  • 减小锁的持有时间
  • 减小锁的粒度
  • 锁的分离
  • 尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字

AQS-源码解析-acquire(int)

  • acquire(int): 此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。这也正是lock()的语义,当然不仅仅只限于lock()。获取到资源后,线程就可以去执行其临界区代码了
  • acquire()的源码,也是AQS的核心AQS
  • tryAcquire()尝试直接去获取资源,如果成功则直接返回
  • addWaiter()将该线程加入等待队列的尾部,并标记为独占模式
  • acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false
  • 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上AQS