原文链接:
https://juejin.cn/post/7096862549634711566
ReentrantLock直译为重入锁,又称为递归锁。
是指在同一个线程中,外部方法获得锁之后,内层的递归方法依然可以获取该锁
倘若锁不具备可重入性,那么我们在第二次获取锁的时候就会造成死锁
复制代码
ReentrantLock的实现是基于AQS的,实现了锁机制和重入机制
ReentrantLock在底层有两种实现方式,分别是公平锁(FairSync)和非公平锁(NonfairSync)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
复制代码
我们在实例化ReentrantLock对象的时候,可以给他传一个boolean类型的变量,如果什么都不传,那么默认生成非公平锁。
我们先来看一看公平锁的具体实现
这是公平锁的简易执行流程
是不是看起来特别简单,所以真正的底层实现也不是很难
//这是FairSync的lock方法,用于获取锁,它在这里直接调用了AQS的加锁方法
final void lock() {
acquire(1);
}
复制代码
这里我们看一下AQS的acquire方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
这里先尝试获取锁,如果获取失败,就会把当前线程加入到线程等待队列,而这里的acquireQueued()和addWaiter()方法分别用于请求入作和封装为Node结点,最后执行selfInterrupt()方法,来中断线程
我们再来看一看NonfairSync(非公平锁)的实现
它的简易流程大概如图:
同样的,我们看一看NonfairSync的lock方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
复制代码
我们可以看到先使用CAS的操作,将当前锁的状态由0改为1,如果该锁的状态不为0,那么和将和公平锁的执行流程一样,直接去调用AQS的acquire方法,如果修改成功的话,证明没有其他线程持有该锁,当前线程可以直接获取该锁。
公平锁直接去调用AQS的acquire方法,而非公平锁先去验证当前锁的状态,倘若为0,修改为1之后,去执行setExculsiveOwnerThread()方法,也就是当前线程持有该锁
说到ReentrantLock的重入性之前我们不妨先了解一下互斥锁和自旋锁
互斥锁:通过阻塞线程来进行加锁,中断阻塞来进行解锁。
自旋锁:线程保持运行状态,用一个循环体不停地判断某个标识的状态来确定加锁还是解锁
复制代码
在上面者两种锁当中,如果出现了一种情况,就是说,如果A线程给资源B加锁了,然后A线程中还有一个子方法C,需要使用资源B,此时,会出现死锁,因为C在给B加锁的时候,会发现B已经被加锁了,此时会导致C阻塞,而可重入锁就是解决的这个问题!
在ReentrantLock中式如何处理重入性的呢?
我们其实很容易想到该怎么实现重入这样的特性,类比我们生活中,就比如我们要从家门口到我们自己的卧室,我们需要先打开大门的锁,进入家里,然后打开客厅的门锁,进入客厅,最后要打开卧室的门锁,进入卧室,这样我们的目的就达成了。
同样的,我们出门就像我们释放锁的过程,我们需要先出卧室然后锁上卧室的门,然后出客厅,锁客厅的门,最后,出大门,锁上大门。
而在ReentrantLock中是通过tryAcqurie()方法完成重入功能的
这里我们以非公平锁的tryAcqurie()方法为例
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;
}
复制代码
与之对应的既然有加锁,那肯定就有释放锁,我们可以看tryRelease()方法
同样的这里我们以非公平锁的tryRelease()方法为例
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;
}
复制代码
在我们使用synchronized的时候,我们需要知道,synchronized修饰的方法,无论执行成功或者异常退出都会释放掉该锁
在synchronized关键字修饰过的方法或者代码块上,在经过编译之后会在它们前后分别生成monitorenter和monitorexit这两个指令,我们来细细道来这两个指令到底是什么意思,什么用途 这两个指令其实就是操作的一个monitor计数器(监听器)对象
当执行这行指令的时候,会查看monitor计数的值,
该指令其实就是释放锁的过程,而该过程也十分简单,就是将monitor的值依次减一直到monitor的值为0
synchronized是非公平锁对象,这样可能导致,新的进程一来就获得了锁,而等待很久的进程迟迟无法获得锁,这样有利于提高性能,但是却可能导致进程饥饿现象
不过synchronized使用起来非常方便直接在方法上添加该关键字就可以了
ReentrantLock完全可以替代synchronized,synchronized在加锁和释放锁的时候是固定的,而ReentrantLock在加锁和释放锁就十分的灵活,并且synchronized只能使用非公平锁,而ReentrantLock可以灵活的使用非公平锁和公平锁!