我们这一篇重点解析的是ReentrantLock,对AbstractQueuedSynchronizer(AQS)的解析会相对较少。所以,如果对AQS的原理还不是很了解的话,建议先去了解AQS的实现原理,然后再来看本篇,一些之前看不太懂的地方应该就会恍然大悟了。
1.ReentrantLock类
我们先来看看ReentrantLock类中大体包含一些什么成员变量和函数:
public class ReentrantLock implements Lock, java.io.Serializable {
//实现同步功能的主要对象,可以从下方的函数调用中看到它有多重要
private final Sync sync;
//Sync是继承自AQS的内部类,在不同的场景中,可以通过实现不同的AQS方法来完成,原版注释中有一句话比较重要:使用AQS的state来表示持有锁的数目
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer{
abstract void lock();
/**
* 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;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
...
}
//实现非公平锁的静态内部类
static final class NonfairSync extends Sync{
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//实现公平锁的静态内部类
static final class FairSync extends Sync{
final void lock() {
acquire(1);
}
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;
}
}
//默认构造返回非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//带参构造可以构造非公平和公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//加锁,由于Sync是FairSync和NonfairSync的父类,所以使用多态调用具体子类中的lock函数
public void lock() {
sync.lock();
}
//尝试图获取非公平锁
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
//释放锁
public void unlock() {
sync.release(1);
}
//创建等待队列
public Condition newCondition() {
return sync.newCondition();
}
...
}
我们一步步来分析ReentrantLock的lock()和unlock()过程:
2. lock过程
//ReentrantLock
public void lock() {
sync.lock();
}
lock执行了sync.lock(),而Sync是FairSync和NonfairSync的父类,Sync中的lock()是一个抽象函数,说明实质是调用的FairSync和NonfairSync中的lock().
我们先以默认NonfairSync为例进行探讨,我们看到NonfairSync中的lock函数,先用compareAndSetState(0,1)利用CAS操作来试探state状态,试探是否已经有线程持有state,如果CAS操作成功,当前线程持有state,代表当前线程可以执行。如果CAS失败,执行acquire(1).
//NonfairSync
final void lock() {
//通过cas操作试探state是否为0,为0则代表当前线程可以占有锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
其中,compareAndSetState()方法是在AQS中进行实现的,它调用了UnSafe中的compareAndSwapInt()方法,compareAndSwapInt()也是一个CAS操作。了解UnSafe应该知道,UnSafe类中主要的内容就是CAS操作,而它里面的CAS操作是通过调用native方法来实现的。
//UnSafe
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
回到正题,接下来要执行的是acquire(1),而acquire()方法是在AQS中进行实现的。这个是AQS中的核心方法了,它完成的工作有同步状态获取,节点构造,加入同步队列,以及自旋等相关工作。
//AQS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire()调用了tryAcquire()方法,这里用到了一种设计模式,没错,模板方法模式:将父类的一些步骤延迟到了子类中进行实现。在NonfairSync中我们可以发现实现好的tryAcquire(),然而它又调用了nonfairTryAcquire(acquires)进行实现,这个方法又在哪呢?
//NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
这个方法nonfairTryAcquire(acquires)在NonfairSync的父类Sync中进行了实现。重点来了…
//Sync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取锁的状态,首先读volatile变量state
int c = getState();
//c等于0表示当前线程可获取锁
if (c == 0) {
//利用CAS操作将state置为1
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为0,则判断当前线程是否拥有锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//如果拥有锁,则将状态位设为state+1
setState(nextc);
return true;
}
return false;
}
至此,加锁lock过程完成。但是在执行nonfairTryAcquire()过程中还是有一些疑惑。
- state是volatile变量,它在执行state=state+1的过程中会不会发生问题?因为我们常常讲的volatile适用的场景有:
1.对变量的写操作不依赖于当前值
2.该变量没有包含在具有其他变量的不变式中扫描二维码关注公众号,回复: 464211 查看本文章
答案是:当然没有问题。如果该线程已经拥有锁,其他线程在探测时会获取锁失败进行等待,所以当前拥有锁的线程对state+1不会发生数据不一致的问题。
值得注意的重点是:state是volatile变量,当我们每次对state进行写操作后,当前线程的工作内存中的共享变量state值会立即刷新到主内存中,保证其他线程对state状态即时可见。当线程要获取state状态时,将线程中的state状态置为无效,然后从主存中读取最新的state状态。这样保证了一个锁不会同时分配给两个线程。
3. unlock过程
unlock的过程和lock的过程其实大体是类似的,一些较为一样的地方,我们就一笔带过了,我们重点详述不同之处:
//ReentrantLock
public void unlock() {
sync.release(1);
}
调用了AQS中的release()方法
//AQS
public final boolean release(int arg) {
//试图释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
调用了tryRelease()方法,和lock中调用类似,使用模板方法模式将该方法延迟到子类中实现。
//Sync
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果state为0,则表示该线程将所拥有的全部锁释放完毕,恢复自由^_^
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//释放锁的最后,写volatile变量state
return free;
}
4. 锁的内存语义
锁在释放的最后对volatile变量进行了写操作,在获取锁之前对volatile变量进行了读操作。释放锁的线程对volatile变量进行的写操作会立即把工作内存中的共享变量刷新到主内存中,在线程对锁进行获取时,同一个volatile变量将立即对获取锁的线程可见。
在上面的lock过程中,我们看到了CAS操作的使用,而且使用了compareAndSetState操作对state进行了更新。CAS操作是:如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。可以发现CAS操作同时具有了volatile读和写的内存语义。
在上一篇博客中,我们说volatile有序性的内存语义:
volatile读之后 的操作不会被重排序到 volatile读之前
volatile写之前 的操作不会被重排序到 volatile写之后
这意味着CAS操作要兼具这两个内存语义,使得CAS前面和后面的内存操作不能重排序。那它是如何实现的?
我们通过上面的lock分析知道,CAS操作最终调用了native方法中的CAS操作。x86的处理器会调用cmpxchg指令来执行CAS操作,在多处理器情况下,还会添加 lock 前缀作为内存屏障。
lock前缀会禁止该指令与之前和之后的读和写指令重排序,并把写缓冲区的数据刷新到主内存中。
5.总结
lock()调用过程:
(1) ReentrantLock:lock()
(2) NonfairSync:lock()
(3) AbstractQueuedSynchronizer:compareAndSetState() / acquire(1)
(4) NonfairSync:tryAcquire()
(5) Sync:nonfairTryAcquire()
unlock()调用过程:
(1) ReentrantLock:unlock()
(2) AbstractQueuedSynchronizer:release()
(3) Sync:tryRelease()
参考:
- 特别感谢《Java并发编程的艺术》
- http://ifeve.com/java-memory-model-5/