ReentrantLock 源码分析

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默认为非公平锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值