前言
文章的目的:帮助对ReentrantLock感兴趣的人,分析ReentrantLock源码,共同进步。
以它的使用开始,介绍ReentrantLock的原理,分析每个方法的用处。
这是我的**"第一个CNDS文章"**,有错误的地方,欢迎指出,谢谢!
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
一、ReentrantLock是什么?
ReentrantLock的作者:Doug Lea,它的作用和Synchronized一样,是为了保证java的原子操作。
但是他们的原理不一样:
Synchronized是JVM层面实现的,需要调用操作系统提供的互斥锁方法。
ReentrantLock是java提供的一个工具类,是API层面的。
二、解析源码的示例代码
import java.util.concurrent.locks.ReentrantLock;
public class TestDemo {
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
test1();
}
private static void test1() {
try{
// 加锁,AbstractQueuedSynchronizer的state属性+1,默认0
reentrantLock.lock();
test2();
}catch (Exception e){
}finally{
// 释放锁,AbstractQueuedSynchronizer的state属性-1,当为0时,才去唤醒其他线程
reentrantLock.unlock();
}
}
private static void test2() {
try{
// 加重入锁---->第二次及以上,在加锁就state+1就行了,不需要再次获得锁
reentrantLock.lock();
}catch (Exception e){
}finally{
// 释放重入锁---->state-1
reentrantLock.unlock();
}
}
}
三、源码解析
3.1 ReentrantLock的类图
Sync是ReentrantLock的一个静态内部类,继承了AbstractQueuedSynchronizer类,
AbstractQueuedSynchronizer是一个抽象的队列同步器,有很多实现用来实现lock锁的方法和属性。
3.2 ReentrantLock的构造方法
public ReentrantLock() {
// 如果不传参数,默认sync属性是是一个非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
// 如果传参数,为true就是公平锁,false为非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
3.3 进入lock方法,以非公平锁来讲解
public void lock() { //当前类:ReentrantLock
// 进入非公平锁NonfairSync的lock方法
sync.lock();
}
final void lock() { //当前类:ReentrantLock.NonfairSync
//将AbstractQueuedSynchronizer的state属性设置为1
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean compareAndSetState(int expect, int update) {
//当前类:AbstractQueuedSynchronizer
//这个是jvm提供的工具类,调用native方法,利用cas乐观锁来替换state的属性为1
//stateOffset是state的村偏移量,如果state的属性为0,则替换为1,替换成功返回true,否则返回false
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
//当前类:AbstractOwnableSynchronizer
//如果compareAndSetState方法返回true,则设置独占锁所属线程属性为当前线程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
上述的代码,如果一个线程获取到锁,则lock方法结束,继续执行业务代码。
假如没有获取锁,compareAndSetState返回false,则执行acquire方法。
3.4 进入acquire方法
//当前类:AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (
!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
)
selfInterrupt();
}
//当前类:ReentrantLock.NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//当前类:ReentrantLock.Sync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//得到state的值
int c = getState();
if (c == 0) { //等于0,代表没有线程持有该锁,下面的操作和3.3标题的代码一样
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
} // 如果state不为0,则代表有线程持有锁,判断持有锁的线程是不是当前线程,是的话state+1,重入锁,不重新获取锁,返回lock方法,继续执行业务代码
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 返回false,代表没有抢到锁,将加入双向链表和挂起
return false;
}
如果**tryAcquire(arg)返回true,则代表重入,则!tryAcquire(arg)为false,acquire方法结束,lock方法结束。否则的话先执行addWaiter(Node.EXCLUSIVE)**方法。
3.5 进入addWaiter方法
//当前类:AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
//生成一个当前线程的Node节点,Node是AbstractQueuedSynchronizer的静态内部类
Node node = new Node(Thread.currentThread(), mode);
//将tail节点指向pred
Node pred = tail;
//如果tail不为空。则证明双向链表已经初始化了,存在head和tail节点
if (pred != null) {
//将当前节点的前节点设置为tail节点
node.prev = pred;
//假设pred为tail节点,则将当前节点设置为tail节点,cas实现
if (compareAndSetTail(pred, node)) {
//如果设置成功,证明当前没有其他线程竞争设置tail节点,或者当前线程抢先设置成功
//然后把原tail节点的后节点设置为当前节点
pred.next = node;
//返回当前节点
return node;
}
}
//双向链表没有初始化或者当前线程没有抢先设置自己为tail节点都会到这里
enq(node);
return node;
}
双向链表没有初始化或者当前线程没有抢先设置自己为tail节点,则会进入enq方法。
3.6 进入enq方法
//当前类:AbstractQueuedSynchronizer
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 如果双向链表没有初始化,则初始化
if (t == null) {
// 建一个辅助节点,设置为双向链表的head节点
if (compareAndSetHead(new Node()))
//设置成功之后,将tail和head指向同一个节点,即双向链表的tail和head节点为同一个Node节点,继续循环将当前线程的节点设置为tail节点
tail = head;
} else {
// 如果tail节点不为null,将当前节点的前节点设置为tail节点,假设t现在还是tail节点,则设置当前节点为tail节点,设置失败继续循环,直到设置成功
node.prev = t;
if (compareAndSetTail(t, node)) {
//设置原tail节点的后节点为当前节点
t.next = node;
//返回当前节点的前节点
return t;
}
}
}
}
addWaiter方法结束,就会返回当前节点,作为acquireQueued方法的参数。
3.7 进入acquireQueued方法
//当前类:AbstractQueuedSynchronizer
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//返回当前节点的前节点
final Node p = node.predecessor();
//如果前节点是head,则去获取锁,和3.4的标题的tryAcquire方法一样
if (p == head && tryAcquire(arg)) {
//如果抢到锁,设置当前节点为头,将该节点包含的node.thread设置为空,前节点设置为空
setHead(node);
p.next = null; // help GC
failed = false;
//返回false,当前线程的lock方法也结束,继续执行业务代码
return interrupted;
}
//这个方法就是对当前节点的前节点的状态进行设置,设置为SIGNAL,代表等待唤醒
//head节点代表当前获取锁的节点,如果它的状态为SIGNAL,就会去唤醒它的下一个节点,这个在unlock方法的源码中,可以看到
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//得到前节点的状态
int ws = pred.waitStatus;
//如果为SIGNAL,就不用设置了,返回true,走接下来的逻辑,挂起就行了
if (ws == Node.SIGNAL)
return true;
//SIGNAL的数值为-1,大于的状态只有一个int CANCELLED = 1;代表取消,所以循环去掉所有取消的状态线程,没必要去挂起了
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//设置当前节点的前节点状态为SIGNAL,代表等待唤醒
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回false,继续进入循环
return false;
}
//此时shouldParkAfterFailedAcquire方法返回true,所有的节点的前节点都为SIGNAL状态了,等待唤醒,调用park方法,挂起线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
此时,所有的等待线程就会形成一个双向链表,由于是非公平锁,所以会按照顺序从head节点往后去唤醒。初始化双向链表会生成下面的这种结构。
3.8 进入unlock方法
//当前类:ReentrantLock
public void unlock() {
sync.release(1);
}
//当前类:AbstractQueuedSynchronizer
public final boolean release(int arg) {
//设置state的值
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//释放锁
unparkSuccessor(h);
return true;
}
return false;
}
//当前类:ReentrantLock
protected final boolean tryRelease(int releases) {
//得到state的值,如果state-1等于0,则证明没有重入锁,或者重入锁已经释放完。
//因为此时还持有锁或者没有去唤醒其他线程,所以所有的操作都是安全的。
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//返回true,接下来去释放锁
free = true;
//设置独占锁的线程属性为null
setExclusiveOwnerThread(null);
}
//设置state的值,如果state不为0,不去释放锁,代表当前unlock方法是一个重入锁调用的
setState(c);
return free;
}
上述的代码,可以只是释放一个重入锁,所以不会走unparkSuccessor方法去唤醒其他线程。
假如state为0了,进入unparkSuccessor方法唤醒其他线程。
3.9 进入unparkSuccessor方法,释放锁
//当前类:AbstractQueuedSynchronizer
private void unparkSuccessor(Node node) {
//传入的是head节点,得到head节点的状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//得到head节点的下一个节点
Node s = node.next;
//如果下一个节点是空,或者状态不正常,为取消状态,int CANCELLED = 1;
if (s == null || s.waitStatus > 0) {
s = null;
//从tail节点向前查找head节点的next节点,如果next节点状态不正常或者不存在,就会找next节点的next节点,一直找下去
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果确实找不到需要唤醒的,就返回unlock方法结束
if (s != null)
//唤醒下一个等待的线程
LockSupport.unpark(s.thread);
}
到这里ReentrantLock的加锁和释放锁的源码就解析完了,一些不太重要的代码,没有解析,感兴趣的可以自己研究。
四、总结
ReentrantLock的源码还是很简单的,最主要的是了解双向链表的结构、了解CAS乐观锁的原理和了解LockSupport的park和unpark方法,这个类是工具类,都是调用的native方法,所以只有了解它的作用就行了。
版权归原作者 鱼跃龙门^我跃架构 所有, 如有侵权,请联系我们删除。