从ReentrantLock到AQS

写在前面

本次主要是根据ReentrantLock源码学习,并掌握部分AQS源码

ReentrantLock

默认构造方法,默认创建非公平锁

public ReentrantLock() {
    
    
     sync = new NonfairSync();
}

可以通过传参来确定公平锁与非公平锁,true:公平锁 false:非公平锁

public ReentrantLock(boolean fair) {
    
    
     sync = fair ? new FairSync() : new NonfairSync();
}

进入源码分析,

lock.lock();

public void lock() {
    
    
   sync.lock();
}

我们先来看看非公平锁

final void lock() {
    
    
   if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
  1. 当有多个线程来竞争这把锁时,多个线程同时调用compareAndSetState方法将state状态置为1,用于抢占这把锁。这是用cas来操作,只会有一个线程cas成功,其余线程会失败。
  2. 抢到锁后需要将当前线程设置到exclusiveOwnerThread,用于锁的重入。
  3. 当其余线程没有争抢到锁后,走else分支。

进入AQS的acquire,独占锁,我们看里面的逻辑

public final void acquire(int arg) {
    
    
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//抽象方法 需要子类去实现 此时先看非公平锁
protected boolean tryAcquire(int arg) {
    
    
    throw new UnsupportedOperationException();
}
//非公平锁子类实现
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;
         if (nextc < 0) // overflow
             throw new Error("Maximum lock count exceeded");
         setState(nextc);
         return true;
     }
     return false;
 }
  1. 此时进入方法,获取下state状态,如果状态等于0说明有线程已经释放锁了,然后利用cas进行抢占锁,如果抢占成功,设置当前线程,直接返回。非公平锁特点,不需要判断队列是否存在等待线程,直接抢占锁资源。
  2. 如果state不等于0或者其余线程cas抢占锁失败,此时会往下走代码。
  3. 先判断当前线程与抢到锁的线程是否是同一个线程,如果是同一个线程,则将state状态+1,返回true。代表锁重入了,结束lock方法。
  4. 如果不是抢占锁的线程,返回false。

回到这个aqs方法
前面返回false才会继续往下走

public final void acquire(int arg) {
    
    
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

addWaiter

private Node addWaiter(Node mode) {
    
    
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
    
    
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
    
    
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

enq

private Node enq(final Node node) {
    
    
    for (;;) {
    
    
        Node t = tail;
        if (t == null) {
    
     // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
    
    
            node.prev = t;
            if (compareAndSetTail(t, node)) {
    
    
                t.next = node;
                return t;
            }
        }
    }
}
  1. Node.EXCLUSIVE 表示独占锁。
  2. 创建一个node节点,node中存在当前线程,并设置独占资源。
  3. 判断下tail尾节点是否为空,如果尾节点为空说明此时没有队列,如果不为空说明已经有线程初始化队列。
  4. 如果队列为空,则需要走enq自旋进行设置队列,为什么要用自旋,因为此时有可能有多个线程同时执行到这段代码,cas失败的需要利用再一次循环直到cas成功。
  5. 进去自旋后此时再次判断尾节点是否为空,为什么还要判断尾节点,还是那个问题,如果多线程执行时已经有一个线程初始化了队列。此时尾节点tail就不为空了,所有操作都要考虑多线程情况下。
  6. 如果尾节点为空,说明队列还没有初始化,此时需要利用cas设置head头节点为new node,此时头节点不会保存任何线程信息。
  7. 尾节点指向头节点。
  8. 如果走到else分支说明当多线程执行此段代码时,有线程初始化了队列成功了进行下一次循环,有的线程cas失败了进入下一次循环进入else,此时多个线程都会进入else分支。
  9. 在else分支中又是一个cas,将当前节点设置为尾节点(此时这个节点才是我们需要的节点,这个节点有线程数据)还是那个说法,多线程竞争,竞争成功cas设置尾节点,竞争失败的进行下一次循环,再次cas直到把当前线程节点加入到尾节点。直到所有线程都将自己的线程数据节点利用cas加循环存入链表中。
  10. 如果 t == null,说明已经有队列了,利用cas将当前节点设置到尾节点。还是cas失败的线程继续循环执行,直到cas成功退出循环。

总结,这段代码就是循环将带有自身线程节点组成一个双向链表。循环加cas主要考虑到多线程情况下。
acquireQueued

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)) {
    
    
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
    
    
        if (failed)
            cancelAcquire(node);
    }
}
  • 看到循环加cas,就是考虑多线程执行问题,cas失败执行下一次循环
  • 获取刚刚创建的node节点的前驱节点
  • 如果前驱节点是头节点,说明我们在链表的第一位(就是head的下一位,head不存线程信息我们不考虑,只是方便设置使用的)。先尝试获取锁,看看能不能获取到锁(锁没有释放或者非公平锁都有可能比队列先抢占到锁)如果获取到锁,把当前节点设置为head节点,并将线程与前置节点都置为空(head的下一个节点置为head节点,并将属性置空),抢到锁直接返回。
  • 如果当前线程不在链表的首位,就是head的下一个节点获取获取锁失败,进入下面的方法。
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
	  int ws = pred.waitStatus;
	   if (ws == Node.SIGNAL)
	       return true;
	   if (ws > 0) {
    
    
	       do {
    
    
	           node.prev = pred = pred.prev;
	       } while (pred.waitStatus > 0);
	       pred.next = node;
	   } else {
    
    
	       compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	   }
	   return false;
	}

shouldParkAfterFailedAcquire方法

扫描二维码关注公众号,回复: 12575336 查看本文章
  • 获取当前线程节点的前置节点,需要判断前置状态是否时-1(signal),只有前置节点是-1时才会唤醒后续节点,就是说如果状态为-1就需要唤醒后续节点
  • 将链表中cancel节点进行删除

parkAndCheckInterrupt

  • 代码就不列出来了,就是挂起当前线程,现在列表中的所有线程都会在此处挂起,等待被唤醒
lock.unlock();

sync.release(1);
//释放锁
public final boolean release(int arg) {
    
    
     //尝试释放锁
    if (tryRelease(arg)) {
    
    
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒lock中链表中阻塞的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

ReentrantLock 的 tryRelease 方法

protected final boolean tryRelease(int releases) {
    
    
    //获取释放后的state值,如果没有重入锁的情况,释放后就是0
    //如果有重入锁的情况,此时释放就不一定是0了
    int c = getState() - releases;
    //必须获取锁的线程才能释放锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果state == 0 说明获取锁的线程将锁释放了,此时需要将状态置空
    if (c == 0) {
    
    
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

AQS的unparkSuccessor

//node 是 head
private void unparkSuccessor(Node node) {
    
    
    //获取head的状态
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    //获取head的下一个节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
    
    
        s = null;
        //如果head的下一个节点为null,从链表后面往前找,找到最前面状态不是cancel状态的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //唤醒head节点的下一个节点线程
        LockSupport.unpark(s.thread);
}

在这里插入图片描述

  • head的下一个节点线程被唤醒,继续循环
  • 判断他的前置节点是否是head,如果是(按照我们现在的逻辑肯定是),尝试获取锁
  • 获取锁成功后,线程继续向下执行,执行业务逻辑
  • 尝试获取锁中公平锁与非公平锁实现是不同的,我们前面也略微说过,我们讲讲这两种锁tryAcquire实现

在这里插入图片描述
公平锁代码

 protected final boolean tryAcquire(int acquires) {
    
    
       //获取当前线程
	   final Thread current = Thread.currentThread();
	   //获取锁状态
	   int c = getState();
	   if (c == 0) {
    
    
	       //
	       if (!hasQueuedPredecessors() &&
	           compareAndSetState(0, acquires)) {
    
    
	           setExclusiveOwnerThread(current);
	           return true;
	       }
	   }
	   else if (current == getExclusiveOwnerThread()) {
    
    
	       int nextc = c + acquires;
	       if (nextc < 0)
	           throw new Error("Maximum lock count exceeded");
	       setState(nextc);
	       return true;
	   }
	   return false;
	}

hasQueuedPredecessors

public final boolean hasQueuedPredecessors() {
    
    
     Node t = tail; 
     Node h = head;
     Node s;
     //如果头不等于尾节点说明队列不为空
     //如果头节点的下一个节点等于null 或者当前线程不等于头节点的下一个节点
     return h != t &&
         ((s = h.next) == null || s.thread != Thread.currentThread());
 }
  • 此处主要判断两个地方,先是判断队列是否为空
  • 再判断头节点的下一个节点是否有数据,头节点的下一个节点是否执行了,按照咱们分析的逻辑,此时头节点的下一个节点不为空,头节点的下一个节点
  • 此时执行的线程就是头节点下一个节点的线程,因为我们刚刚唤醒了这个线程

所以
h != t 队列不为空,true
(s = h.next) == null 头节点的下一个节点不为空,false
s.thread != Thread.currentThread() 想在执行的就是头节点下一个节点的线程,返回false

队列头节点开始获取到锁,将锁的状态置为1,并设置当前线程为获取锁的线程

非公平锁代码

        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;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
  • 不需要判断是否是队列的头,直接获取锁,此时有可能有一个新的线程来跟队列头节点的线程来竞争,非公平锁此时需要竞争,竞争成功则获取到锁。
  • 如果是公平锁,新的线程判断队列有数据,并且不属于队列的头节点,不会去竞争锁。

以上就是重入锁的代码底层实现原理,如有漏洞欢迎批评指正

猜你喜欢

转载自blog.csdn.net/qq_37904966/article/details/112644533