前言
学过Java的人应该都用过synchronize关键字去实现锁的操作,但是对于lock的底层机制,你可能不知道,今天我将把ReentrantLock的源码深入分析一下,让大家从底层理解锁,理解ReentrantLock。
简介
ReentrantLock是一种重入锁,就是支持重复进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁的公平和非公平选择。
框架
AbstractQueuedSynchronizer类是一个队列同步器,用来构建锁或者其他同步组件的基础框架。它使用了一个int变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。同步器提供所有的实现机制。这里需要提前知道几个属性类型以及其描述,这样对ReentrantLock的理解会更加简单。
waitStatus属性 | 描述 |
CANCELLED | 值为1;由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,节点进入该状态后不会变化 |
SINGAL | 值为-1;后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行 |
CONDITION | 值为-2;节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用singal()方法后,该节点会从等待队列中转移到同步队列中,加入到同步状态的获取中 |
PROPAGATE | 值为-3;表示下一次共享式同步状态获取将会无条件的传播下去 |
INITIAL | 值为0;初试状态 |
构造方法
默认无参数构造方法,就是默认false,就是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
有参数构造方法,可以传入true,就为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
源码分析非公平锁
·非公平锁:就是排队的场景,但是有人来了就直接插队,不按顺序来。
public void lock() {
sync.lock();
}
进入lock方法,简单的一行,但是里面蕴含的深意却很多。往下面看
final void lock() {
if (compareAndSetState(0, 1))//CAS操作,更新成1
//当前线程设置为持有锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
//锁已经被其他人获得
acquire(1);
}
非公平锁的实现,sync定义了实现的机制。这里有一个acquire方法。这个方法是独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回。否则,将会进入同步队列等待。下面是这个方法的实现,这个方法同时还会重写父类的tryAcquire(int arg0)方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//这里三个方法下面会解释
selfInterrupt();
}
①首先是tryAcquire方法,这个方法是独占式获取同步状态,该方法需要查询当前状态,并判断同步状态是否符合预期,然后再进行CAS设置同步状态
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) {//没有任何线程获取到锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//设置为持有锁的线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//重入的状态
int nextc = c + acquires;//重入次数加1
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);//更新state
return true;
}
return false;
}
②当tryAcquire返回失败的时候,就会来到下面这个方法
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;//node的前置结点指向尾巴结点
if (compareAndSetTail(pred, node)) {//CAS操作设置尾结点,代码如下
pred.next = node;//当前node指向尾节点下一个结点
return node;//返回node
}
}
enq(node);//空队列,直接入队
return node;
}
上面那个方法就是实现了入队操作,下面有直接入队的方法。
rivate Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize//队列没东西
if (compareAndSetHead(new Node()))//新建结点指向head结点
tail = head;//头尾都指向这个结点
} else {//队列有东西
node.prev = t;//传入node指向尾巴结点
if (compareAndSetTail(t, node)) {//node设置为尾巴结点(tail指针指向node)
t.next = node;//构造双向链表,
return t;
}
}
}
}
这个方法就实现了空地列,直接入队的操作。
③acquireQueued方法,传入的是上面返回的node和状态1
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)) {//意味链表只有两个结点,
//head指向原来的那个结点,tail指向添加进来的node
setHead(node);//head指向node
p.next = null; // help GC//原来的head置空
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire和parkAndCheckInterrupt尝试进入获取失败进人挂起状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
上面那个方法里又有两个尝试进入获取失败并进入挂起的状态的方法,下面给出相应的源码实现
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//waitStatus初始值为0,还有很多值。。。
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置为SIGNAL:-1
}
return false;
}
非公平锁实现加锁的操作的源码就是上面这些,多看看就会理解其中的深意。而对于释放锁的操作就简单多了,下面是其实现源码
ublic void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//如果当前线程完全释放了这把锁
Node h = head;//头指针指向head
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
上面的release方法是一个独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。这个方法中的tryRelease方法,则是独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//当前重入次数为0
free = true;
setExclusiveOwnerThread(null);//当前持有锁的线程设置为null
}
setState(c);//设置状态c
return free;
}
而下面的方法则实现了,将同步队列中第一个节点包含的线程唤醒的实现。
rivate void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {waitStatus>0是取消的状态
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)//找状态是SINGLE:-1的结点
s = t;//距离头结点最近的的非取消状态结点
}
if (s != null)
LockSupport.unpark(s.thread);//如果结点不为空,唤醒线程
}
上面就是ReentrantLock其非公平锁源码的实现,下个博客将会对其公平锁的实现源码做深入的分析。