ReentrantLock 源码分析

来源:互联网 发布:外国法制史 知乎 编辑:程序博客网 时间:2024/06/06 00:01

ReentrantLock 加解锁源代码分析

ReentrantLock是java同步JUC的一个重要功能,下面我们就从源代码入手,详细分析它的加解锁过程。

ReentrantLock.lock() 加锁

public void lock() {// 其中sync为 Sync -> AbstractQueuedSynchronizer(为了方便下文简称AQS),说明Sync是基于AQS实现的。// 对于Sync的实现又有NonfairSync和FairSync sync.lock();}

NonfairSync 加锁流程

/** * 执行加锁 * 首先:尝试修改state状态,如果修改成功表示获取锁,将独占线程修改为当前线程 * 其次:如果修改失败,则进入正常的加锁流程 */final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);}
/** * 以独占的方式获取锁,并且忽略中断。 * */public final void acquire(int arg) { // 如果没有获取成功,则将当前线程添加到队列尾部然后将当前线程中断 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires);}
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //没有线程持有锁 if (c == 0) { //以CAS的方式去获取锁,如果获得成功,则将独占线程设置为当前线程 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //是否可重入 else if (current == getExclusiveOwnerThread()) { // 如果是当前线程持有锁,那么state就是当前线程获取锁的次数,每当获取一次锁,状态计数器加1 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // 这里没有并发,直接set 计数器的值 setState(nextc); return true; } return false;}
/*** 根据给出的模式为当前线程创建入队节点* 有独占和共享两种模式,对于ReentrantLock来说,是独占模式*/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;}
/** * 初始化队列并将node插入到队尾 */private Node enq(final Node node) { for (;;) { Node t = tail; // 如果队列为空,新建一个空节点来初始化队列 if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { // 队列初始化完成后将node插入到队尾 node.prev = t; if (compareAndSetTail(t, 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是不是第二个节点,如果是,则尝试获取锁,如果获取成功则将node置为首节点,返回false 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); }}
/** * 从方法名上理解,如果锁获取失败了应该阻塞当前线程 */private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) //Node.SIGNAL = -1; /* * 如果为node的前继节点为SIGNAL,那么当前继节点释放锁后,会唤醒后继(当前)节点,那么node可以安全阻塞 */ return true; if (ws > 0) { // 前继节点取消 /* * 前继节点被取消,则跳过前继节点重试 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 否则把前继节点的状态设置SIGNAL,然后下一次进来是直接返回true。利用LockSupport.park()方法将线程阻塞然后再中断。 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false;}
private final boolean parkAndCheckInterrupt() { // 将当前线程阻塞 LockSupport.park(this); return Thread.interrupted(); //返回线程是否中断}

FairSync 公平锁

公平锁的加锁流程就是非公平锁开始获取锁失败的,进入正常流程的环节,具体加锁流程参考非公平锁的部分。

final void lock() { acquire(1);}

ReentrantLock.unlock() 解锁

通过上面的加锁流程,我们可以知道,无论是公平锁还是非公平锁,它们获取锁的过程其实就是当前线程将state的状态由0变为1(同一个线程重入就是将state状态累加),如果当前线程没有获取锁就将给当前线程创建一个node节点添加到等待队列的末尾,并且将当前线程阻塞,直到前一个线程释放锁并且唤醒当前线程的过程。由此推断解锁的过程无非也就是将state状态由1到0(同一线程可重入的时候就是将正整数递减到0),并且唤醒后继线程的过程。以下是源代码分析:

public void unlock() { sync.release(1);}
public final boolean release(int arg) { if (tryRelease(arg)) { // 如果释放成功 Node h = head; // 释放锁后,如果还有线程在等待锁,则唤醒首节点(首节点是个空节点)后面的一个节点 if (h != null && h.waitStatus != 0) // unparkSuccessor(h); return true; } return false;}
protected final boolean tryRelease(int releases) { int c = getState() - releases; //如果当前线程没有重入,那么这里的getState应该等于1 if (Thread.currentThread() != getExclusiveOwnerThread()) //只有拥有锁的线程才能释放锁 throw new IllegalMonitorStateException(); boolean free = false; //如果没有重入,一次释放 if (c == 0) { free = true; setExclusiveOwnerThread(null); } //如果重入了,那么state递减, setState(c); return free;}
private void unparkSuccessor(Node node) { int ws = node.waitStatus; //如果还有线程在等待锁,那么首节点的waitStatus=-1 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 这里先将它置为默认值 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); //唤醒没有被取消的节点的线程}

相比加锁流程,解锁流程还是很easy的。至此,ReentrantLock的加解锁的流程就分析完了,顺便提下,ReentrantLock默认为非公平锁。