ReentrantLocks意为可重入锁,也就是获得锁的同一个线程可以多次获得锁而不会阻塞。但是同一时间内锁只能被同一个线程持有,同时ReentrantLock又分为公平锁和非公平锁,但是他们都是通过维护一个节点队列来实现,只不过公平锁每次都取头结点执行,而非公平锁每次可能随机取节点执行。无论那种情况,每当一个线程获得锁之后都会使状态加一,当状态为0时会通知后面排队的节点(或者是在非公平锁中某一个正好合适的线程执行)。
/**
* 一个抽象类,是锁同步控制的基础。子类有公平锁和非公平锁两种。
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* 默认是非公平锁.
*/
abstract void lock();
/**
* 非公平锁尝试获取资源.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//状态为0,表示还没有线程获得锁
if (compareAndSetState(0, acquires)) {//CAS方式设置资源
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;//没获得资源,返回false
}
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;//释放成功返回true
}
protected final boolean isHeldExclusively() {//当前线程是否是获得了排他属性的线程
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {//当前线程锁的次数
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {//判断是否有锁
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
Syn的两种实现方式:公平锁和非公平锁。公平锁的语义和非公平锁的语义不同在于,公平锁每次从队列的首部获取等待最久的线程,而非公平锁则每次随机获取线程执行,很可能某一个线程多次获取锁,导致其他线程饥饿。在下面代码中可以看出,非公平锁首先使用CAS设置状态,也就是说如果锁是空就直接占有,然后进入acquire进行处理。而公平锁则直接进入acquire(1).
/**
* 非公平锁
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))//CAS设置当前为0 的时候上锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//否则尝试获得锁。
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 公平锁
*/
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) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {//没有前驱节点并且CAS设置成功
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;
}
}
acquire中的详细介绍。acquire是AQS类中的方法。其中tryAcquire就是(非)公平锁类下的tryAcquire方法,若没有获得资源那么就继续执行,否则结束此函数。addWaiter用于添加节点,也就是把当前线程对应的节点插入CLH队列的尾部。
/**
* 这个方法也就是lock()方法的关键方法。tryAcquire获得资源,返回true,直接结束。若未获取资源,新建一个节点插入队尾,
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//获取资源立刻结束
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//没有被中断过,也结束
selfInterrupt();
}
/**
* 为当前线程和模式创建一个节点,这个也是AQS中的类。
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//设置pred为尾节点这里尝试一次。
if (pred != null) {//尾节点不为空则设置新节点前驱为尾节点
node.prev = pred;
if (compareAndSetTail(pred, node)) {//CAS原子操作将node设置为新的尾节点
pred.next = node;
return node;//pred下一个节点为新的尾节点。并返回新的尾节点
}
}
enq(node);//上面没成功,将节点插入队尾
return node;
}
/**
*把一个节点插入队尾中
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;//将尾节点赋值给t
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//尾节点为空CAS设置新节点为头结点
tail = head;
} else {
node.prev = t;//node 前置节点设置为t
if (compareAndSetTail(t, node)) {//CAS操作设置node 为尾节点
t.next = node;
return t;//返回新的尾节点。
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//表示当前线程在休眠过程中有没有被中断过
for (;;) {//循环获取前驱节点,一直到前驱是头结点,并且某个节点获取了资源
final Node p = node.predecessor();//获得node节点的前驱节点
if (p == head && tryAcquire(arg)) {//p节点为头结点,当前线程获得了资源
setHead(node);//当前节点设置为头结点,取消排队
p.next = null; // 把node 设置为null
failed = false;
return interrupted;//返回是否中断过
}
if (shouldParkAfterFailedAcquire(p, node) &&//判断当前线程是否应该阻塞
parkAndCheckInterrupt())//阻塞当期线程,在这里执行park操作
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 检查自己在没有获得资源之后,是不是应该挂起。
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获得前置节点的状态
if (ws == Node.SIGNAL)
/*
* 如果前置节点的状态是sigal,那么就可以返回true,也就是意味着线程可以被阻塞了。
*/
return true;
if (ws > 0) {
/*
* 如果前置节点的状态是删除状态,那么就一直找到一个正常的状态排在它后面。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
*如果前驱正常,就把前驱状态设置为sigal
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
这里简单总结一下lock的过程:
首先尝试获取资源,如果当前状态为0,表示没有线程占有锁,设置该线程为独占模式,使用CAS设置状态,否则如果当前线程和独占线程是一个线程,修改状态值,否则返回false。
若获取资源失败,则通过addWaiter方法创建一个节点并放在CLH队列的尾部。
逐步去执行CLH队列中的线程,当前线程会公平性的阻塞一直到获取锁为止,返回线程在等待的过程中还是否中断过。
unLock方法比较简单,直接release(1)即可 。
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放锁,其实就是状态减一。
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//成功则唤醒后继节点
return true;
}
return false;
}
// 线程已被取消
static final int CANCELLED = 1;
// 当前线程的后继线程需要被unpark(唤醒)
// 一般发生情况是:当前线程的后继线程处于阻塞状态,而当前线程被release或cancel掉,因此需要唤醒当前线程的后继线程。
static final int SIGNAL = -1;
// 在Condition休眠状态,在等待Condition唤醒
static final int CONDITION = -2;
// (共享锁)其它线程获取到“共享锁”,对应的waitStatus的值
static final int PROPAGATE = -3;
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();//持锁线程和当前线程不一致抛出异常
boolean free = false;
if (c == 0) {//状态为0 说明锁已经不被线程所占有
free = true;
setExclusiveOwnerThread(null);//设置占有锁线程为null
}
setState(c);//设置锁的状态
return free;
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
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.
*/
int ws = node.waitStatus;
if (ws < 0)//获取当前节点状态,若小于0则置状态为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);//唤醒之
}
一次unlock操作需要修改状态位,然后唤醒节点。整个释放操作也是使用unpark()来唤醒队列最前面的节点。其实lock中比较重要的也就是lock和release,它们又和AQS联系紧密,下面会单独谈谈AQS的重要方法。
参考资料:
http://ifeve.com/juc-aqs-reentrantlock/
http://ifeve.com/java-special-troops-aqs/
http://www.cnblogs.com/skywang12345/p/3496147.html#p24
https://www.cnblogs.com/waterystone/p/4920797.html