ReentrantLock原码学习:偏心锁
书接上文,偏心锁和非偏心锁的不同,次要体现在锁的竞争形式不同。
偏心锁在获取锁的时候,同样分三种状况:第一种:在查到以后锁状态为0的时候(即无线程持锁),并不会立刻去竞争锁,而是先通过hasQueuedPredecessors()
办法判断CLH锁队列中是否还有期待已久的线程,如果有,那么以后线程乖乖去锁队列的尾部去排队,考究个先来后到;如果队列中没有期待线程,那么以后线程就去竞争锁,胜利后,将以后线程设置为锁的持有者;如果锁状态不为0,判断是不是本人持有了锁,如果是,则批改锁状态加1,这就是锁重入;第三种,锁被占有,但不是本人持有,那么乖乖去CLH锁队列尾部排队。
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //判断以后的CLH锁队列中是否还有排在后面的线程 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; }
锁的开释
无论是偏心锁还是非偏心锁,开释锁的形式都是对立的,通过批改AQS中锁状态state的值(每次开释减1)来开释,当state为0时,将AQS中ExclusiveOwnerThread
置为null,示意以后线程开释掉锁。
rotected final boolean tryRelease(int releases) { int c = getState() - releases; //判断开释锁的线程是否是持锁线程 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //锁状态为0,则将ExclusiveOwnerThread置空,锁被开释 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); //批改锁状态, return free; }
开释锁胜利后,须要唤醒CLH锁队列中的后继节点。这里由父类AQS中实现:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; //如果HEAD节点存在,期待状态不是初始化状态,就唤醒HEAD的后继节点 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
偏心锁与非偏心锁总结
- 无论是偏心锁还是非偏心锁,都是可重入锁,判断持锁线程是本人后,批改锁状态state,且在第一次获取锁时,ExclusiveOwnerThread置为以后线程;
- 开释锁的时候,也是批改锁状态state的值,为0的时候,ExclusiveOwnerThread置空,示意锁开释,同时会唤醒AQS的锁期待队列中,HEAD节点的后继节点(waitStatus<=0,未勾销的节点);
- 偏心锁在竞争锁的时候,会先判断CLH锁队列中是否有期待已久的线程,有,则放弃本次竞争,作为尾节点插入CLH队列期待唤醒,考究先来后到;
- 非偏心锁在竞争锁的准则是:只有以后锁未被持有,就去竞争,不必管队列里是否有线程期待;