lock锁中有一段代码:
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; } }
这段代码表示了多线程在竞争锁的逻辑。
其中,对于
else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; }
这段代码来说,为什么没有加上同步机制?那是因为AQS提供了获取得到当前锁的线程,以及同步器状态的记录,通过这两个成员,可以知道是锁正在被哪个线程所持有。这里可以通过一个图来进行说明
而至于为何又会有nextC,那是因为可能存在着某种特殊的场景:
public void A(){ lock.lock(); xxxxx; B(); lock.unlock(); } public void B(){ lock.lock(); lock.unlock(); }
也就是说在执行A的之后,又会调用方法B来进行执行,那么对于此时,对于同一个线程来说,就会两次持有锁,那么这就是上面的nextc相加的原因了。
同理可以推断,syncronized也是可重入的
排队方法:
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; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
从这里可以看到,造就了一个CLH队列。Node节点中保存了对当前线程的引用
对于Node节点中的几个重要属性:
pre,next,waitstatus,thread,mode
首先来说一下:
mode表示的有两种形式:一种是独占也就是互斥,另外一个是共享;
waitstatus代表着当前节点的生命状态,有五种生命状态,分别代表着不同的情况
private Node enq(final Node node) { // 所有的线程都得进来进行排队,不允许漏掉任何一个 // 不然底层JVM创建好的线程栈等等信息就会造成内存泄漏等情况 for (;;) { 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; } } } }
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
这个方法转化一下:
// 当前进来的线程节点 acquireQueued(node, arg))
此时此刻代表的是进来排队的线程排好队之后,需要来进行阻塞了。
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; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
在第一轮循环中,只是修改了前驱节点的状态,也仅仅只是做了一个修改而已,第二个节点及其后续的没有法神改变。表示的首结点可以被唤醒;第二轮循环才真正的表示的是可以被其进行阻塞,也就是把所有的线程都给阻塞掉了。
同时判断线程是否会有中断线程来进行唤醒的?
ReetrantLock是一个可以中断的锁
shouldParkAfterFailedAcquire(p, node)
在这个方法中,首先会将node的节点,也就是队首的节点的waitstatus从0->-1,这样做是因为想要在公平锁的情况下,当线程占有的锁被释放后,需要判断队首的节点的状态是否是-1,如果是-1,表示的可以被唤醒。
然后被唤醒后会再走一轮循环,然后拿到锁之后了再去进行执行。
但是这里可能存在着非公平的情况下,如果新来的线程和队首的线程同时来进行抢,那么这个时候会需要问题,位于队首的节点可能会再次进入到阻塞状态,那么再次把0修改成-1,放入到队列中来进行排列。再此经历过两轮循环,才能够重新恢复到-1状态