一、简介:
ReentrantLock的实现是基于其内部类FairSync(公平锁)和NonFairSync(非公平锁)实现的。 其可重入性是基于Thread.currentThread()实现的:如果当前线程已经获得了执行序列中的锁, 那执行序列之后的所有方法都可以获得这个锁。
在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。
二、ReentrantLock的可重入性分析
ReentrantLock重入性是基于Thread.currentThread()实现的: 如果当前线程已经获得了锁, 那该线程下的所有方法都可以获得这个锁。ReentrantLock的锁依赖只有 NonfairSync和FairSync两个实现类, 他们的锁获取方式大同小异。
//可重入性的实现基于下面代码片段的 else if 语句 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { ... // 尝试获取锁成功 } else if (current == getExclusiveOwnerThread()) { // 是当前线程,直接获取到锁。实现可重入性。 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
此处有两个值需要关心:
//持有该锁的当前线程 private transient Thread exclusiveOwnerThread; -----------------两个值不在同一个类---------------- /** * 同步状态 * 0: 初始状态-无任何线程得到了锁 * > 0: 被线程持有, 具体值表示被当前线程持有的执行次数 * 这个字段在解锁的时候也需要用到。 * 注意这个字段的修饰词: volatile */ private volatile int state;
三、ReentrantLock锁的实现分析
ReentrantLock 的公平锁和非公平锁都委托了AbstractQueuedSynchronizer#acquire去请求获取。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
公平锁和非公平锁在锁的获取上都使用到了volatile 关键字修饰的state字段, 这是保证多线程环境下锁的获取与否的核心。但是当并发情况下多个线程都读取到 state == 0时,则必须用到CAS技术,一门CPU的原子锁技术,可通过CPU对共享变量加锁的形式,实现数据变更的原子操作。volatile 和 CAS的结合是并发抢占的关键。
公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; }
其中hasQueuedPredecessors是用于检查是否有等待队列的。
public final boolean hasQueuedPredecessors() { 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()); }
非公平锁在实现的时候多次强调随机抢占:
if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } }
与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁。如果被加入了等待队列后则跟公平锁没有区别。
ReentrantLock锁的释放是逐级释放的,也就是说在可重入性场景中,必须要等到场景内所有的加锁的方法都释放了锁, 当前线程持有的锁才会被释放。
释放的方式很简单, state字段减一即可:
protected final boolean tryRelease(int releases) { // releases = 1 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; }
当前拥有锁的线程释放锁之后, 且非公平锁无线程抢占,就开始线程唤醒的流程。
通过tryRelease释放锁成功,调用LockSupport.unpark(s.thread); 终止线程阻塞。
private void unparkSuccessor(Node node) { // 强行回写将被唤醒线程的状态 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; // s为h的下一个Node, 一般情况下都是非Null的 if (s == null || s.waitStatus > 0) { s = null; // 否则按照FIFO原则寻找最先入队列的并且没有被Cancel的Node for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 再唤醒它 if (s != null) LockSupport.unpark(s.thread); }
try { lock.lock(); i ++; } finally { lock.unlock(); }
可以发现哪怕在不使用 volatile关键字修饰元素i的时候, 这里的i 也是没有并发问题的。
volatile 是Java语言的关键字, 功能是保证被修饰的元素(共享变量):