ReentrantLock在Java中是可重入锁
可重入锁:即递归锁,指的是在同一线程,外层函数获得锁之后,内层递归函数仍然有获得该锁的代码,但不受影响
ReentrantLock公平锁和非公平锁:
ReentrantLock锁实现了Lock接口,里面具体锁的实现使用了抽象内部类Sync。Sync有两个实现类,NonfairSync和FairSync,即公平锁和非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁和非公平锁定义:
公平锁是多个线程获取锁时,依次获取锁,谁排队时间长谁获取锁,符合FIFO原则;
非公平锁是谁抢到锁就是谁获取锁,先等待的可能后获取锁
非公平锁:
static final class NonfairSync extends ReentrantLock.Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//线程进来后,判断当前锁是否被持有,如果没有,获取到锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//线程没有获取到锁
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
public final void acquire(int arg) {
// 如果在tryAcquire方法中依然获取锁失败,当前线程加入同步队列中等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
在上述代码中,线程尝试获取锁,如果获取失败,调用AQS中的acquire方法,如果依然获取失败,进入同步队列中等待。
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前锁的状态
int c = getState();
//如果锁没有被持有,就尝试获取锁,不管同步队列有没有等待的线程
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程即为持有锁的线程,获取到锁,state+1
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;
}
在nonfairTryAcquire方法中,线程尝试获取锁,不用进行排队等待,如果获取不到,加入同步队列
在锁的释放后,在AQS队列唤醒下一个线程,被唤醒线程和新线程需要重新进行竞争锁,这时线程需要通过CAS获取锁,并不能保证被唤醒的线程可以抢到锁,因此是非公平锁。
公平锁:
在FairSync 中,与非公平锁的不同的地方在于,这里直接调用的AQS的acquire方法,没有先尝试获取锁,因为要保证公平性,就要确保同步队列中没有等待的线程时,才可以获取到锁,主要在tryAcquire中实现。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//判断当前AQS的同步队列中是否还有等待的线程
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;
}
}
该方法多了一个判断hasQueuedPredecessors,这个方法是判断当前AQS的同步队列中是否还有等待的线程如果有,返回true,否则返回false。
当队列中没有等待的线程时,当前线程才能尝试通过CAS的方式获取锁,否则就让这个线程去队列后面排队,以此来保证公平。
公平锁的效率不如非公平锁,但可以减少饥饿发生概率。
可重入锁:
ReentrantLock的实现基于队列同步器AQS,ReentrantLock的可重入功能基于AQS的同步状态:state,通过修改AQS中state的值来同步锁的状态。,通过这个方式,来实现可重入。
当锁没有被持有时,state为0,当某一线程获取锁后,state+1,并记录下当前持有锁的线程,再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,state再+1,如果不是,则阻塞线程,
当线程释放锁时,state-1,当state值减为0时,表示当前线程彻底释放了锁,然后将记录当前持有锁的线程的那个字段设置为null,并唤醒其他线程,使其重新竞争锁。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取state的值
int c = getState();
// 如果state的值等于0,当前没有线程持有锁 将state的值改为1,修改成功,则成功获取锁,并设置当前线程为持有锁的线程,返回true
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 判断当前线程是否等于持有锁的线程,如果等于,将state的值+1,并设置到state上,获取锁成功,返回true
如果不是当前线程,获取锁失败,返回false
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果不是当前线程,获取锁失败,返回false
return false;
}
释放锁:
public void unlock() {
// 调用AQS的release方法释放资源
sync.release(1);
}
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的release方法,让队列在释放完成一个锁后,立即唤醒同步队列下一个节点去竞争锁