简介
ReentrantLock可以实现公平锁和非公平锁两种,可以从他的构造方法看出,默认无参构造方法创建的是非公平锁,公平锁可以通过指定构造方法参数为true设置
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平和非公平是通过抽象内部类Sync实现的,他有两个子类NonfairSync(非公平)和FairSync(公平)两种,而Sync又继承自AbstractQueuedSynchronizer,简称AQS, AQS为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
公平锁和非公平锁
公平锁指按照先到先得的顺序排队,线程之间不能随意争抢资源,ReentrantLock中的公平锁在尝试获取锁之前会先判断有没有其他在等待获取锁的线程,如果没有才去获取锁
非公平锁意思就是线程抢夺资源不受顺序限制,只要锁被释放,所以线程都可以尝试抢占,哪个线程抢到哪个先执行。ReentrantLock中的非公平锁实现是,抢到锁的线程开始执行,其他等待的线程会加入等待队列,一旦锁被释放就一起开始尝试抢占。
非公平锁源码
lock()
非公平锁的lock方法代码是NonFairSync类中的lock方法的实现,首先判断锁是否被持有,如果没有,则当前线程获取到锁,如果已经被持有则尝试获取锁。
final void lock() {
//通过cas的方式判断state值是不是0,是则更新为1
//state表示当前线程锁被占用的状态,0表示没有线程占用,1表示被线程
//占用,大于1则表示被同一个线程占用并重入多次
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
setExclusiveOwnerThread()
protected final void setExclusiveOwnerThread(Thread thread) {
//exclusiveOwnerThread是当前持有锁的线程
exclusiveOwnerThread = thread;
}
如果当前已经有线程持有了锁,通过tryAcquire方法尝试获取锁,如果没有获取到,则addWaiter将线程节点加入等待队列,然后acquireQueued再次尝试获取锁,获取不到则线程挂起等待被唤醒。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法要看在NonFairSync中的实现,这个方法比较简单理解,看注释
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//判断state值,等于0表示当前没有线程持有锁,直接设置当前
//线程为独占锁
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是不是持有锁的线程,如果是则将state+1,表示重入次数+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
//nextc小于0表示重入次数超过int最大值,抛出异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
tryAcquire方法返回false则没有获取到锁,这时候将线程节点加入链表
private Node addWaiter(Node mode) {
//创建线程节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//tail表示当前链表尾节点,第一次加入的时候因为链表还没有创建,为null
Node pred = tail;
if (pred != null) {
//如果此时链表中已经有节点了,那么把上一个尾节点设置为当前节点的前节点
node.prev = pred;
//将当前节点设置为新的尾节点,并返回当前节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//第一次会走到这里
enq(node);
return node;
}
//第一次创建链表
private Node enq(final Node node) {
for (;;) {
//第一次创建尾节点为null
Node t = tail;
if (t == null) { // Must initialize
//创建一个空节点设置为头节点
if (compareAndSetHead(new Node()))
//尾节点指针也指向这个头节点
tail = head;
} else {
//再次判断,如果此时有了尾节点,可能其他线程新插入的
node.prev = t;
//把当前节点的前节点指定为尾节点,
if (compareAndSetTail(t, node)) {
//设置上一个尾节点的后节点为当前节点,同上,相互指向
t.next = node;
//返回当前节点
return t;
}
}
}
}
addWaiter将节点加入链表后返回这个节点,再调用acquireQueued方法,这个方法就是使获取不到锁的线程挂起等待被唤醒,不间断的去获取已经入队队列中的当前节点的前节点的状态,如果前节点的状态为大于0,则代表当前节点被取消了,会一直往前面的节点进行查找,如果节点状态小于0并且不等于SIGNAL则将其设置为SIGNAL状态,设置成功后将当前线程挂起,挂起线程后也有可能会反复唤醒挂起操作
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取这个节点的前节点
final Node p = node.predecessor();
//如果前节点就是头节点,再次尝试获取锁,获取到则更新头节点为当前节点
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断waitStatus状态,如果为SINGNAL,则执行parkAndCheckInterrupt()方法挂起线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
waitStatus的取值
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
//此节点已经设置了状态,要求对当前节点进行挂起操作
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//如果前节点被取消,则将取消节点移除队列操作
//找到当前节点的前节点,如果前节点为取消节点则一直往前寻找一个节点。pred.waitStatus > 0标示已取消
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//waitStatus=0或者PROPAGATE时,表示当前节点还没有被挂起停止,需要等待信号来通知节点停止操作。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
第一次进来waitStatus是默认值0,然后会调用compareAndSetWaitStatus(pred, ws, Node.SIGNAL)设置waitStatus为SINGNAL,表示当前线程需要等待被唤醒,返回false,然后进入第二次循环,线程如果仍然没有获取到锁,那么再次进入shouldParkAfterFailedAcquire,此时由于waitStatus是SINGNAL了,那么方法返回true,执行后边的parkAndCheckInterrupt方法挂起线程,到这里这个线程的操作就中止了,直到被唤醒,返回线程中断的状态Thread.interrupted()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
到这里lock的逻辑就结束了
unlock()
看一下解锁的源码,unlock直接调用的是AbstractQueueSynchornizer的release方法
public final boolean release(int arg) {
//调用ReentrantLock中的Sync里面的tryRelease方法,尝试释放锁
if (tryRelease(arg)) {
//获取头节点
Node h = head;
//头节点不为空且状态不为0时进行unpark方法
if (h != null && h.waitStatus != 0)
//唤醒下一个未被取消的节点
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//重入次数减1
int c = getState() - releases;
//unlock方法必须和lock在同一线程调用,否则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果重入次数为0了,说明锁已经被当前线程释放
if (c == 0) {
//unlock成功,否则c!=0则失败
free = true;
setExclusiveOwnerThread(null);
}
//更新state值,
setState(c);
return free;
}
当tryRelease方法成功的时候,那么当前线程就成功的释放锁了,此时需要唤醒下一个未被取消的线程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//获取节点的waitStatus状态
int ws = node.waitStatus;
//// 如果小于0则设置为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 唤醒下一个节点,唤醒下一个节点之前需要判断节点是否存在或已经被取
//消了节点,如果没有节点则不需唤醒操作,如果下一个节点被取消了则一
//直找到一个没有被取消的节点。
Node s = node.next;
//如果下一个节点为空则直接返回不唤醒任何节点,如果下一个节点被取消
//了,那么它会从尾节点往前进行遍历,遍历与头节点最近的没有被取消的
//节点进行唤醒操作
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
被唤醒的下一个节点是在上边parkAndCheckInterrupt方法中挂起的,被唤醒后会再次进入循环,尝试再次获取锁,如果仍然没有获取到会再次进入挂起等待下一次唤醒
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取这个节点的前节点
final Node p = node.predecessor();
//如果前节点就是头节点,再次尝试获取锁,获取到则更新头节点为当前节点
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断waitStatus状态,如果为SINGNAL,则执行parkAndCheckInterrupt()方法挂起线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
公平锁源码
看完了非公平锁源码,再看公平锁就简单很多了,大部分逻辑是相同的,那么是哪里体现出公平和非公平的差异?
第一点
非公平锁在lock方法执行的时候代码如下,先判断当前锁有没有被持有,没有则直接占有这个锁,如果已经有了线程持有这个锁,则再尝试去获取锁,也就是说,从第一行判断来看,不论之前有没有线程在等待这个锁,只要有机会,他就会抢占这个锁资源
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我们再看一下公平锁的lock方法,公屏锁去掉了第一步的插队行为,先在acquire方法中判断有没有排队的线程
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
第二点
区别在tryAcquire方法中,非公平锁的实现是这样的,再次判断当前锁有没有被占用,没有则直接占有锁,不管别的,如果当前线程和占用锁的线程是同一个,则更新state值
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) {
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;
}
而公平锁的tryAcquire方法是这样的,就算当前锁没有被线程占有,他也不会直接抢占锁,而是先执行了hasQueuedPredecessors,这个方法就是在判断当前有没有线程在等待这个锁,只有没有其他线程在等待的情况下他才会占有锁,有等待的线程则排队,后边就会被加入链表
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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,判断有没有线程比当前线程等待锁等了更长的时间,先到先得
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁和非公平锁的差别就是这两点