1. concurrent 包的层次结构

java.util.concurrent下,包的目录结构图:

image.png

从整体上来看 concurrent 包的整体实现图如下图所示:
image.png

2. Lock

java SE5 之后,并发包中增加了 lock 接口,它提供了与 synchronized 一样的锁功能。虽然它失去了像 synchronize 关键字隐式加锁解锁的便捷性,但是却拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。通常使用显示使用lock的形式如下:

  1. Lock lock = new ReentrantLock();
  2. lock.lock();
  3. try{
  4. .......
  5. }finally{
  6. lock.unlock();
  7. }

注意:**synchronized** 同步块执行完成或者遇到异常是锁会自动释放,而 **lock** 必须调用 **unlock()** 方法释放锁,因此在**finally** 块中释放锁

2.1 Lock 的接口 API

  1. void lock(); //获取锁
  2. void lockInterruptibly() throws InterruptedException//获取锁的过程能够响应中断
  3. boolean tryLock();//非阻塞式响应中断能立即返回,获取锁放回true,反之返回fasle
  4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//超时获取锁,在未超时或者未中断的情况下能够获取锁
  5. Condition newCondition();//获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回


`
public class ReentrantLock implements Lock, java.io.Serializable**`

当你查看源码时你会惊讶的发现 ReentrantLock 并没有多少代码,另外有一个很明显的特点是:基本上所有的方法的实现实际上都是调用了其静态内存类 **Sync** 中的方法,而 **Sync** 类继承了**AbstractQueuedSynchronizer(AQS)**
可以看出要想理解 ReentrantLock 关键核心在于对队列同步器AbstractQueuedSynchronizer(简称同步器)的理解。

2.2 初识 AQS

同步器是用来构建锁和其他同步组件的基础框架,它依赖:

  1. int 成员变量标识同步状态 state ```java /**
  • The synchronization state. */ private volatile int state; ```
  1. 一个 FIFO(First-In First-Out 先入先出)队列构成的等待队列

它的子类必须重写 AQS 的几个 protected 修饰的用来改变同步状态的方法,其他方法主要是实现了排队和阻塞机制。状态的更新使用 **getState****setState****compareAndSetState** 这三个方法

锁和同步器的关系:

同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。
锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作

image.png

state变量 -> CAS -> 失败后进入队列等待 -> 释放锁后唤醒


一个线程过来 进行 cas AQS 里的 state(默认是0),如果成功,state 更新成1,加锁线程=线程1。 此时其他线程过来cas失败,进入等待队列。

2.3 AQS 的模板方法设计模式

AQS 的设计是使用模板方法设计模式,它将一些方法开放给子类进行重写,而同步器给同步组件所提供模板方法又会重新调用被子类所重写的方法。举个例子,AQS 中需要重写的方法 tryAcquire

  1. protected boolean tryAcquire(int arg) {
  2. throw new UnsupportedOperationException();
  3. }

ReentrantLock 中 NonfairSync(继承AQS)会重写该方法为:

  1. protected final boolean tryAcquire(int acquires) {
  2. return nonfairTryAcquire(acquires);
  3. }

而 AQS 中的模板方法 acquire():

  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4. selfInterrupt();
  5. }

可以归纳总结为这么几点:

  1. 同步组件(这里不仅仅指锁(LockReentrantReadWriteLock),还包括 CountDownLatchThreadPoolExecutor 等)的实现依赖于同步器 AQS,在同步组件实现中,使用 AQS 的方式被推荐定义继承 AQS 的静态内存类;
  2. AQS 采用模板方法进行设计,AQS 的 protected 修饰的方法需要由继承 AQS 的子类进行重写实现,当调用AQS 的子类的方法时就会调用被重写的方法;
  3. AQS 负责同步状态的管理,线程的排队,等待和唤醒这些底层操作,而 Lock 等同步组件主要专注于实现同步语义;
  4. 在重写 AQS 的方式时,使用 AQS 提供的 getState(),setState(),compareAndSetState()方法进行修改同步状态;

AQS可重写的方法如下图(摘自《java并发编程的艺术》一书):
初识 Lock 与 AQS - 图5

在实现同步组件时AQS提供的模板方法如下图:
初识 Lock 与 AQS - 图6

AQS提供的模板方法可以分为3类:

  1. 独占式获取与释放同步状态;
  2. 共享式获取与释放同步状态;
  3. 查询同步队列中等待线程情况;

同步组件通过AQS提供的模板方法实现自己的同步语义。

资料