Lock思想:
a朋友想进门,b朋友也想进门,还有c,d,e等一堆堆朋友,这个时候就要去锁管理处。获取锁,一个锁同时只能被一个朋友拿到。这个锁管理处,还有一个队列,一旦获取不到锁,就让等待锁的人排队,等别人归还了锁,就从队列中踢出新的小朋友去拿锁。
类比说明:
各位小朋友:多个线程a,b,c,d...
锁管理处:ReentrantLock+sync
锁管理处的队列:AbstractQueuedSynchronizer
锁:ReentrantLock的一个私有变量state的值。
拿锁:通过cas原子方法,来设置state的值。
Lock方法主要流程:
final void lock() {
if (compareAndSetState(0, 1))//直接去放锁的地方尝试拿锁(通过cas设置state为1),这就是为啥叫做不公平锁,上来先不排队,先直接去抢锁。
setExclusiveOwnerThread(Thread.currentThread());//成功则设置当前线程
else
acquire(1);//cas失败,说明别人(也有可能是自己)已经获取锁,进入正常获取锁流程。
}
acquire(1)方法:
public final void acquire(int arg) {//
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire尝试直接获取锁,acquireQueued排队等锁。两者任一成功,则拿锁成功,返回acquire方法。
selfInterrupt();//都失败的话,就将当前线程中断。
}
tryAcquire(1)调用以下方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//若state为0,则说明锁已经空闲
if (compareAndSetState(0, acquires)) {//试图直接获取一下。
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//虽然state不为0,但是,之前获取锁的就是自己线程本身。
int nextc = c + acquires; //锁的状态+1
if (nextc < 0) // overflow,如果小于1,说明自己获取太多了,获取的次数已经溢出了整数范围
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; //为false,说明目前是别的线程在获取着锁。上段代码的tryAcquire(arg)就会为false。
}
addWaiter(Node.EXCLUSIVE), arg):
/**
*@param mode 要插入队列的节点
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//Node这个Class表示等待队列中的节点。
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {//队列不为空的情况下,
node.prev = pred;//直接在queue的尾部加入当前这个需要等待的节点。
if (compareAndSetTail(pred, node)) {//并设置tail为这个节点。
pred.next = node;
return node;//成功加入queue尾部后,返回这个新节点。
}//若失败。则说明队列已经发生了改变。则去enq(node)。这个 if (pred != null)逻辑是为竞争不激烈的情况准备的。
}
enq(node);//这个就要为竞争激烈或者队列为空的情况准备。
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize,tail为空,说明等待队列中无人,则试图初始化一个head节点,
if (compareAndSetHead(new Node()))
tail = head;//初始化tail和head均指向该node。
} else {//若tail不为空,则说明queue不为空,则正常将node加入queue。
node.prev = t;
if (compareAndSetTail(t, node)) {//试图设置tail为新的node。
t.next = node;
return t;//成功的话则将该节点放入queue,失败的话,说明queue已经改变,进行下一波循环,直到设置成功(竞争激烈的情况,就会多次循环,不激烈的时候就一次循环搞定,此处是通过cas保证原子性)
}
}
}
}
缕一下奥:
获取锁的步骤(unfairLock):
1.cas直接试图设置state为1。成功转8
2.查看之前拿锁的人是自己,还是别人。是别人转3,是自己转7。
3.将当前线程包装为一个nodeA,查看queue是空还是非空。空转4,非空转5
4.初始化queue的head为一个初始化nodeHead,tail为当前nodeA,加入queue成功,返回nodeA。
5.直接加入队列queue。并返回nodeA。
6.走到这步,说明等待队列queue已经加入成功了,接下来,开始排队等锁,也就是acquireQueued(Node )。
7.千辛万苦等待到了锁,返回acquire(1)。
8.拿锁成功。
接下来是acquireQueued(Node )
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);//将head指针前移,
p.next = null; // help GC,并断掉之前的head。
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//第一个方法就是判断状态是否为signal,是的话,就可以在第二个方法将这个线程park(就是阻塞),不需要不断循环判断,只需要阻塞,静静的等待被唤醒(具体怎样被唤醒,参看unlock()方法),不是signal的话,就看是否为别的状态,是0的话,说明是初始化,将状态设置为signal,是cancell的话,遍历前面的节点看有木有cancell,有的话从链中将那些节点全部去掉。第二个方法,就是将线程park,等待唤醒。
interrupted = true;//走到这一步,说明,节点在等待期间,该线程已经中断了。
}
} finally {
if (failed)//该方法一旦有什么异常,保证要放弃获取锁。
cancelAcquire(node);
}
}
unlock思想:
获取锁时,只可能有一个线程持有锁,但是一个线程可能重入锁。unlock时,如果不再持有锁,就让下一个等待者去拿锁,如果还是持有状态,则单纯将state-1就可以。
unLock方法较简单,主要流程:
public final boolean release(int arg) {
if (tryRelease(arg)) {//state-1后,若为0,说明,当前线程不再持有锁,进入if
Node h = head;
if (h != null && h.waitStatus != 0)//且queue不为空,有等着锁的线程呢
unparkSuccessor(h);//将之前queue中被park的node,唤醒,让他去尝试拿锁。
return true;
}
return false;//state-1后,若不为0,则单纯返回false
}