ReentrantLock , 可重入锁, 和 synchronized 有着相同的内存语义
比较下这俩者之间的相同以及区别
相同点:
俩者有着相同的内存语义,
1、当线程获取锁时,JMM会把线程对应的本地内存置为无效,然后临界区的代码从主存中读入共享变量到工作内存。
2、 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
区别在于,ReentrantLock是应用层面, 是通过volatile来实现同样的语义
不同点:
synchronized是jvm内置锁,ReentrantLock是应用层面实现的锁,所以ReentrantLock在运用方面会更灵活
1、ReentrantLock提供了 锁查询(tryLock)、锁等待 等方法,可以有效的防止死锁,synchronized只能阻塞了
2、ReentrantLock提供了可以中断的加锁机制(lockInterruptibly),synchronized不响应中断
3、ReentrantLock提供了公平锁模式,synchronized只有非公平锁
4、ReentrantLock支持更加灵活的同步代码块,但是这也导致了一定的风险,一定要保证在finally中处理锁释放,synchronized则没有这方面担忧,是由jvm控制
性能方面:
这一块很多人有误解,认为ReentrantLock一定比synchronized要优秀,实际上在Java1.6之前确实是这样,不过从Java1.6开始,synchronized做了很多性能优化,已经不比ReentrantLock差.
而且随着jvm的发展和完善,synchronized的性能会越来越好,毕竟它是jvm内置的锁,在扩展优化方面有着优势.
java 1.8版的ConcurrentHashMap就是用cas和synchronized完成的
如何选择
1、大部分情况下,如果ReentrantLock和synchronized都能满足需求,优先选择synchronized.
2、如果有锁嵌套的情况下,可以考虑使用ReentrantLock的锁查询、锁等待等机制,防止死锁
3、特殊场景需要用到公平锁,考虑使用ReentrantLock
4、要使用多个条件队列的情况下,考虑使用ReentrantLock的condition
接下来我们分析下ReentrantLock的源码,先看下部分类图结构
从图中可以看出ReentrantLock是用内部类(Sync)继承了AQS, 然后在这个基础上实现了俩个子类, NonfairSync(非公平锁),FairSync(公平锁)
因为是继承了AQS,所以ReentrantLock只需要在这个基础上完善tryAcquire、tryRelease方法即可,接下来以非公平锁为例,看下源码部分
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//非公平锁实现具体的tryAcquire逻辑,因为是独占锁,这里的acquires传入1
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取同步状态state,如果为0说明此时没有竞争,可以尝试获取同步状态
int c = getState();
if (c == 0) {
//通过cas 将状态设置为tryAcquire
if (compareAndSetState(0, acquires)) {
//因为是独占锁,所以成功后,记录下获取同步状态的线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为0,说明已经有线程获取了同步状态,检查是否是当前线程获取
else if (current == getExclusiveOwnerThread()) {
//如果是的话,则获取同步状态,并且state+1,表示重入的次数
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
以上是尝试获取同步状态的方法,成功返回true,失败返回false, 如果返回false,AQS将会将该线程放入锁同步队列中阻塞等待(详情看这里AbstractQueuedSynchronizer解析)
//释放同步状态,因为是独占锁这里的releases为1
//因为是可重入锁,这里state表示重入次数,释放一次state-1
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//如果获取同步状态的线程不是当前线程,则抛IllegalMonitorStateException
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果state为0说明同步状态已完全释放
if (c == 0) {
free = true;
//在这里将之前记录获取同步状态的线程清除
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
上面是尝试释放同步状态的方法,成功返回true,失败返回false,如果为true,AQS将会唤醒同步锁队列中的线程,继续竞争同步状态
ReentrantLock支持公平锁,看下这部分实现逻辑
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
//该方法在类FairSync下,逻辑和非公平锁基本一致
//唯一的区别是在竞争同步状态之前,需要判断队列中是否有其它正在等待的线程
//如果有,那么只能放弃竞争老老实实的进入队列排在最后面
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//重点在这里,多了一个hasQueuedPredecessors方法
//判断同步锁队列中是否还有其他正在等待的节点
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;
}
}
}