一、ReentrantLock概述
ReentrantLock是API层面的互斥锁(lock()/unlock()方法配合try/finally配合使用);
ReentrantLock高级功能:等待可中断、可实现公平锁,以及锁可以绑定多个条件;
ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer(简称之为AQS),其中AQS使用一个整型的volatile变量(命名为state)来维护同步状态,马上我们会看到,这个volatile变量是ReentrantLock内存语义实现的关键。
abstract static class Sync extends AbstractQueuedSynchronizer {... ...}
二、ReentrantLock和Synchronized的区别
ReentrantLock是API层面的互斥锁;
Synchronized是原生语言支出的互斥锁,是可重入锁;
ReentrantLock可以分为公平锁和非公平锁,底层由乐观策略解决阻塞同步的问题,也被称为乐观锁;
Synchronized是只要不去做正确的同步措施就会出错,无论数据共享是否真的存在竞争,因此被称为悲观锁;
三、ReentrantLock的实现
ReentrantLock的公平锁加锁过程:
ReentrantLock:lock()
FairSync:lock()
AbstractQueuedSynchronizer:acquire(int arg)
ReentrantLock:tryAcquire(int acquires)
在上面的自上而下的顺序中,只有偶在tryAcquire(int acquire)这个才是真正的开始加锁:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 获取锁的开始,首先读volatile变量state
if (c == 0) {
if (isFirst(current) &&
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;
}
ReentrantLock公平锁解锁过程:
ReentrantLock:unlock()
AbstractQueuedSynchronizer:release(int arg)
Sync:tryRelease(int releases)
在上面的自上而下的顺序中,只有偶在tryRelease(int release)这个才是真正的开始加锁:
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); // 释放锁的最后,写volatile变量state
return free;
}
ReentrantLock非公平锁的释放和公平锁完全一样,所以这里仅仅分析非公平锁的获取:
ReentrantLock:lock()
NonfairSync:lock()
AbstractQueuedSynchronizer:compareAndSetState(int expect,int update)
ReentrantLock非公平锁加锁原理:
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
在这里我们看到使用了compareAndSet这个方法来完成volatile的states变量这个状态的更新;
我们把compareAndSet()方法称为CAS,也就叫比较替换法,在锁种被称为乐观锁;
JDK对compareAndSet()描述是:如果当前状态值等于预期值,则以原子方式将同步状态,则设置为给定的更新值;
四、CAS实现原子操作的三大问题
1、ABA问题
问题产生原因:如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
解决办法:使用版本号:1A=>2B=>3C
java1.5JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
public boolean compareAndSet(
V expectedReference, // 预期引用
V newReference, // 更新后的引用
int expectedStamp, // 预期标志
int newStamp // 更新后的标志
)
2、循环时间长,开销大
问题产生的原因:CAS自选锁在长时间执行不成功的话会一直执行,对于内存消耗非常大。
解决办法:使用pause指令。
1)、它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间;
2)、它可以避免在退出循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空(CPU Pipeline Flush),从而提高CPU的执行效率。
3、只能保证一个共享变量的原子操作
问题产生原因:多个共享变量操作时,循环CAS就无法保证操作的原子性
解决办法:加锁