ReentrantLock公平锁与非公平锁的区别及源码解析
一、获取锁对比
FairLock:
- 判断当前锁状态state是否为0.为0表示锁空闲,判断当前线程之前是否已经有线程在等待获取锁了,没有才能获取;
- 所有线程都在排队,有序的等待获取锁,不会出现饥饿现象;
- 因为所有线程都在等待获取锁,因此都产生阻塞,唤醒阻塞消耗资源。
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
//判断如果未能获取锁
if (!tryAcquire(arg) &&
//addWaiter将该线程添加到CLH的队列尾部,因为是FIFO队列,获取锁是从头获取,acquireQueued获取队列,休眠等待被唤醒,获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//设置中断标志,acquireQueued方法的parkAndCheckInterrupt方法
//使用了interrupted()测试中断的方法,该方法测试如果该线程存在中断情况,则返回true,并且将中断标志设置false,
//所以如果之前被中断过,这里要再次设置中断,若没被中断,则也不必执行此方法
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//若锁处于空闲状态
if (c == 0) {
//判断当前线程之前是否有等待的线程
if (!hasQueuedPredecessors() &&
//CAS判断如果当前锁为空闲即可获取,设置锁状态已被获取,有点类似单例模式的双重检测
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;
}
}
NonfairLock:
- 判断当前锁状态state是否为0.为0表示锁空闲,只要锁空闲就能获取;
- 如果一直都存在插队获取锁的现象,那么后续线程可能就无法获取锁,产生饥饿现象;
- 因为非公平锁因此产生的阻塞比较少,效率较高。
final void lock() {
//CAS判断如果当前锁为空闲即可获取,设置锁状态已被获取
if (compareAndSetState(0, 1))
//设置当前线程为锁拥有者
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
//判断如果未能获取锁
if (!tryAcquire(arg) &&
//addWaiter将该线程添加到CLH的队列尾部,因为是FIFO队列,获取锁是从头获取,acquireQueued获取队列,休眠等待被唤醒,获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//设置中断标志,acquireQueued方法的parkAndCheckInterrupt方法
//使用了interrupted()测试中断的方法,该方法测试如果该线程存在中断情况,则返回true,并且将中断标志设置false,
//所以如果之前被中断过,这里要再次设置中断,若没被中断,则也不必执行此方法
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//若锁处于空闲状态
if (c == 0) {
//CAS判断如果当前锁为空闲即可获取,设置锁状态已被获取,有点类似单例模式的双重检测
//这里对比公平锁没有!hasQueuedPredecessors() 判断,所以是非公平的,只要锁空闲就能获取
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;
}
二、释放锁对比
都是一样的释放机制,先释放锁,然后唤醒后继线程
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//判断锁是否释放
if (tryRelease(arg)) {
Node h = head;
//头节点就是释放锁线程的节点
//waitStatus的状态值:
//1. CANCELLED(1):因为超时或者中断该节点取消等待,节点将一直保持这个状态,需要注意的是,线程取消等待后将不会再阻塞
//2. SIGNAL(-1):当前要释放锁的线程节点的后继节点处于阻塞状态,所以当当前线程释放锁或者取消等待的情况下必须唤醒它的后继节点
//3. CONDITION(-2):该节点是处在一个条件队列中,直到将该节点的状态设置为0从原队列转移到等待队列中等待唤醒(这个值在这里与其他用途无关,只是简化了结构)
//4. PROPAGATE(-3):一个被释放的节点可以唤醒后继节点,也可以传播似的唤醒后继节点的后继节点
//5. 0:新入队节点初始化为0
if (h != null && h.waitStatus != 0)
//唤醒后继等待的节点
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//每释放一次状态减1,因为重入锁,所以可能有大于1的情况
int c = getState() - releases;
//如果要释放锁的线程不是当前锁拥有的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//锁状态为0的时候锁完全释放
if (c == 0) {
free = true;
//设置锁拥有线程为空
setExclusiveOwnerThread(null);
}
//设置释放一次后的锁状态
setState(c);
return free;
}
//唤醒后继节点
private void unparkSuccessor(Node node) {
//获取锁的等待状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//如果后继节点不存在或者已经取消等待
if (s == null || s.waitStatus > 0) {
s = null;
//循环从队尾开始获取最近的未取消等待的节点
//因为在addWaiter中node.prev = pred;先设置前驱节点,然后原子比较后再设置后继节点,pred.next = node;
//如果有新节点加入的时候node.prev = pred设置了,但是pred.next = node还未设置,这样的话获取到的节点pred.next有可能是空的情况
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒线程
LockSupport.unpark(s.thread);
}
版权声明:本文为weixin_44919928原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。