0%

java并发02_AQS&锁的实现

LookSupport工具类

JDK中的 rt.jar 包里面的 LockSupport 是个工具类,它的主要作用是挂起和唤醒线程该工具类是创建锁和其他同步类的基础

park()方法–阻塞

如果调用 park 方法的线程已经拿到了与 LockSupport 关联的许可证 ,则 调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。

unpark(Thread thread)方法–唤醒

当一个线程调用 unpark 时,如果 参数 thread 线程没有持有 thread 与 LockSupport 类关联的许可证, 则让 thread 线程持有。 如果 thread 之前因调用 park()而被挂起,则调用 unpark 后,该线程会被唤醒。如果 thread 之前没有调用 park,则调用 unpark 方法后, 再调用 park 方法,其会立刻返回 。

AQS概述

state

AQS 中维持了一个单一的状态信息state,可以通过 getState 、setState 、 compareAndSetState 函数修改其值 。

  • state用volatile修饰,保证多线程中的可见性。
  • getState()和setState()方法采用final修饰,限制AQS的子类重写它们两。
  • compareAndSetState()方法采用乐观锁思想的CAS算法,也是采用final修饰的,不允许子类重写。

对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;
对于读写锁ReentrantReadWriteLock来说,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;
对于semaphore来说,state用来表示当前可用信号的个数:
对于CountDownlatch来说,state用来表示计数器当前的值。

CLH队列

CLH(Craig, Landin, and Hagersten locks) 同步队列 是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。

AQS依赖它来完成同步状态state的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

Node

Node节点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS队列的;

waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点);

prev记录当前节点的前驱节点,next记录当前节点的后继节点。

独占or共享

在独占方式下,获取与释放资源的流程如下:

  • 当一个线程调用acquire(intarg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
1
2
3
4
5
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
  • 当一个线程调用release(intarg)方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。
1
2
3
4
5
6
7
8
9
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

**AQS类并没有提供可用的tryAcquire和tryRelease方法,需要由具体的子类来实现。**子类在实现tryAcquire和tryRelease时要根据具体场景使用CAS算法尝试修改state状态值,成功则返回true,否则返回false。

和独占方法类似,共享方式下则是acquireShared(int arg),releaseShared(int arg)。

条件变量ConditionObject

类似于notify和wait,ConditionObject的signal和await方法也是用来配合锁(使用 AQS 实现的锁)实现线程间同步的基础设施。

ReentrantLock原理

ReentrantLock最终还是使用AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。

jdk8中ReentrantLock的公平锁实现,主要是重写了tryAcquire方法,判断队列是否为空,队列为空直接获得锁,否则放入同步队列中等待。

jdk8中ReentrantLock的非公平锁实现,NonfairSync类调用了父类Sync的nonfairTryAcquire方法,如果当前锁状态为0,会先去尝试获取锁,获取失败才会加入等待队列中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

ReentrantReadWriteLock原理

ReentrantReadWriteLock原理和ReentrantLock差不多,同样支持公平和非公平锁。

ReentrantReadWriteLock巧妙地使用AQS的状态值的高 16 位表示获取到读锁的个数,低16位表示获取写锁的线程的可重入次数,并通过CAS对其进行操作实现了读写分离,这 在读多写少的场景下比较适用。

读锁不支持条件队列

StampedLock锁

StampedLock是并发包里面JDK8版本新增的一个锁,该锁提供了三种模式的读写控制。
当调用获取锁的系列函数时,会返回一个long型的变量,我们称之为戳记(stamp),这个戳记代表了锁的状态。
其中try系列获取锁的函数,当获取锁失败后会返回为0的stamp值。
当调用释放锁和转换锁的方法时需要传入获取锁时返回的stamp值。

StampedLock的读写锁都是不可重入锁,所以在获取锁后释放锁前不应该再调用会获取锁的操作,以避免造成调用线程被阻塞。