0%

Java并发面试题整理

AQS 全称 AbstractQueuedSynchronizer,是 java.util.concurrent.locks 包下面的一个类。用来构建锁和同步器的框架,使用 AQS 能简单而高效地构造出应用广泛的大量同步器,如ReentrantLock,Semaphore,ReentrantrantReadWriteLock,SynchronousQueue,FutureTask 等。开发者也可以利用 AQS 构造出符合要求的同步器。

AQS 的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态。如果被请求的共享资源被占用,就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin, and Hagersten)队列是一个虚拟的是双向队列(虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系)。AQS 是将每条共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。使用 CAS 对该同步状态进行原子操作实现对其值的修改。

共享方式

  1. Exclusive(独占)只有一个线程能执行,如 ReentrantLock。独占锁又分为公平锁和非公平锁。公平锁按照线程在队列中的排队顺序,先到者先获取锁;非公平锁在线程需要获取锁时,无视队列顺序直接去抢锁,谁抢到算谁的。
  2. Share(共享):多个线程可同时执行,如 Semaphore、CountDownLatch、CyclicBarrier、ReadWriteLock

实现原理

同步器设计基于模板模式,自定义同步器一般的方式为:

  1. 继承 AbstractQueuedSynchronizer 并重写指定方法,这些方法是对共享资源 state 的获取和释放
  2. 将 AQS 组合在自定义同步组件的实现中,并调用其它模板方法,这些模板方法会调用使用者重写的方法

AQS 使用模板方法模式,自定义同步器时重写以下几个 AQS 提供的模板方法:

1
2
3
4
5
isHeldExclusively(); // 线程是否在独占资源,用到 condition 时才需要实现
tryAcquire(int);// 独占方式,尝试获取资源,成功则返回 true,失败则返回 false
tryRelease(int);// 独占方式,尝试释放资源,成功则返回 true,失败则返回 false
tryAcquireShared(int);// 共享方式,尝试获取资源,负数表示失败;0 表示成功,但没有剩余可获取资源;正数表示成功,并且有可用资源
tryReleaseShared(int);// 共享方式,尝试释放资源成功返回 true,失败返回 false

默认情况下,每个方法都抛出UnsupportedOperationException,这些方法的实现必须是线程安全,并且应该简短而不阻塞。AQS 中的其它方法都是 final的,无法被其它类使用。

CyclicBarrier 和 CountDownLatch 的区别

CountDownlatch 是计数器,只能使用一次,计数为 0 时释放所有等待线程;CyclicBarrier 的计数器提供 reset 功能,可以使用多次。