概念: 队列同步器AQS是AbstractQueuedSynchronizer的简称,是JUC的核心类.AQS使用了一个int类型的变量表示同步状态,通过内置的FIFO队列来完成线程获取资源的排队工作.
	AQS是实现锁的关键.简单来说,它为并发包的各个组件加锁解锁提供了底层支持.

AQS原理面试题的核心回答要点

  • state状态的维护
  • CLH队列
  • ConditionObject通知
  • 模板方法设计模式
  • 独占与共享模式
  • 自定义同步器
  • AQS全家桶,如ReentrantLock等

AQS的类图结构

AQS结构

AQS主要维护的数据结构包括一个state状态量以及一个FIFO(先进先出)队列CLH(即Craig, Landin, and Hagersten).CLH队列里的元素是Node节点,里面封装了线程.

state源码设计的回答要点:

  • state用volatile修饰,保证多线程中的可见性
  • 对state的get,set方法都用final修饰,限制子类重写它们
  • compareAndSetState()方法调用UnSafe类的CAS方法,也是采用final修饰.
  • state>0表示已经获取了锁,state=0表示无锁

CLH队列要点:

  • 先进先出的双向队列,队列的元素是Node节点,当一个线程获取锁失败时,同步器会将该线程以及等待状态等信息构造成一个Node并放入CLH队列中.Node节点也可以理解为正在排队获取锁的线程.
  • 首节点是成功获取锁的Node,获取锁失败的线程会进入队列成为尾节点.
  • Node入队需要保证线程安全,因此设置尾节点时使用了CAS操作,需要比较当前线程"认为"的尾节点与当前节点.如果此处由于并发问题导致CAS操作失败,则调用enq()方法,自旋加上CAS保证一定入队成功.
  • 队列中由于只有首节点才会出队,无竞争因素,因此出队不需考虑线程安全.
  • 首节点出队后,会将state设置为0解锁.
  • 节点进入同步队列之后就进入了一个自旋的过程,判断其前驱节点是否首节点,若是则尝试获取锁.这样就保证了只有头节点才能获取到锁,保证了FIFO的规则.

共享模式的实现:

  • 当节点获取锁之后,不会改变state,只会唤醒后继节点然后出队,每个被唤醒的节点在被唤醒的同时也会唤醒下一个节点,因此让可能成功获取锁的节点都来尝试获取锁,以实现共享状态的"向后传播"

ConditionObject队列与CLH队列

  • ConditionObject的作用是让线程进入等待通知状态,在不满足某条件时挂起线程,直到被另一线程唤醒.
  • 为单向队列
  • 当CLH的头节点的线程调用了await()方法后会出队,释放锁并加入conditionObject等待队列中.
  • 当某线程调用了该conditionObject的signal()方法后,等待队列的firstWaiter(头节点)会被唤醒进入CLH队列.

CountDownLatch

该类使一个线程等待其余多个线程各自执行完毕后再执行.

A线程调用await()方法进入阻塞态,计数为N,此后当其他线程共计调用了N次countDown()方法后才唤醒A线程.

CountDownLatch属于共享锁,维护了一个AQS的子类Sync,创建一个CountDownLatch对象时,所传入的整数n就会赋值给state属性,当countDown()方法调用时,该线程就会尝试对state减一,而调用await()方法时,当前线程就会判断state属性是否为0,如果为0,则继续往下执行,如果不为0,则使当前线程进入等待状态,直到某个线程将state属性置为0,其就会唤醒在await()方法中等待的线程

CountDownLatch和CyclicBarrier区别:

  • countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次,不能reset.强调一等多.(班长收作业,等大家都交完了才能交给老师)
  • CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用.强调多等一(等待最后一个) (田径比赛,所有选手都到达终点之后才能进行颁奖)

Semaphore

用来控制可同时访问临界区的线程数.调用tryAcquire()获取信号量,release()方法释放资源.

共享锁


CyclicBarrier

其主要逻辑:若有线程未到达栅栏位置,到达栅栏位置的线程一直等待状态,直至发生以下场景: ①. 所有线程都到达栅栏位置 ②. 有线程被中断 ③. 线程等待超时 ④. 有线程调用reset()方法,断开当前栅栏,将栅栏重置为初始状态

在线程池中使用CyclicBarrier时一定要注意线程的数量要多于CyclicBarrier实例中设置的阻塞线程的数量就会发生死锁。 调用await()方法的次数一定要等于屏障中设置的阻塞线程的数量,否则也会死锁。


ReentrantLock

  • 默认非公平锁,效率会比公平锁高很多

  • 可重入锁,外层方法获取锁不会阻塞内层方法(同一个线程可以重复获取锁)

公平锁加锁的过程:

  • 首先判断AQS中的state是否为0,为0表示锁是闲置的.
  • 然后调用hasQueuedPredecessors()方法判断CLH队列中是否有其他线程,若有则不会尝试获取锁(非公平锁没有此步,),若无则用CAS将state置1来获取锁,并将当前线程设置为获取锁的独占线程setExclusiveOwnerThread(current).
  • 如果state不为0,则判断当前线程是否为独占线程if (current == getExclusiveOwnerThread()),若是则获取锁(可重入性)
  • 若state既不为0,当前线程也非独占线程,则无法获取锁,依次调用addWaiter()加入CLH队列中,acquireQueued()方法来自旋获取锁.

节点进入队列后的操作:

  • 死循环进行以下两步操作
  • 首先根据node.predecessor()来判断上一个节点是否头节点,若是则尝试获取锁.
  • 若上一个节点并非头节点或者获取锁失败,则调用shouldParkAfterFailedAcquire(),根据上个节点的waitStatus来判断是否需要挂起当前线程.
  • 如果等待过程中出现了异常,并且还没有获取锁,则会取消这次获取锁的请求.

解锁过程:

  • 首先判断当前线程是否独占线程,若不是则抛出异常.
  • 然后重置独占线程为null
  • 然后state-1后若为0则表示锁可以释放了,唤醒被挂起的线程.若state不为0则说明当前线程仍未完全释放锁,不唤醒其他线程而是直接返回

FutureTask

futuretask

主要功能:

  • 异步执行任务,可以通过get()获取任务的结果
  • 可以开始,取消以及查看任务是否完成
  • 如果任务没有执行完,get方法会导致调用的线程阻塞
  • 可以作为Runnable被线程执行,也可以作为Callable获取任务结果.
  • 一旦一个执行任务已经完成就不能再次开始和结束(除非执行时调用runAndReset( ) 方法)
  • 配合线程池的submit()使用,该方法的传参可以是FutureTask类型或者Runnable类型,不过Runnable类型会被适配器转换为FutureTask

BlockingQueue