ReentrantLock锁源码分析

//分析的是公平锁加锁过程....
//尝试去拿锁
public final void acquire(int arg) {
    //如果没拿到锁 拿到锁返回true 注意这里取反
    if (!tryAcquire(arg) &&
         //当前线程加入到aqs 此时还没拿到锁就阻塞 里面还会判断自己是否是第一个排队
         //尝试去拿锁 拿到锁失败时会去将当前节点的上一个节点的等待状态设置为-1 此时返回false 
         //再次尝试去拿锁(总共尝试两次去拿锁) 拿不到时会将当前线程阻塞 调用park()
        //addWaiter()添加当前线程在aqs
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

 


 

//添加等待的线程到aqs 维护aqs链表得关系
private Node addWaiter(Node mode) {
   //初始化一个节点
    Node node = new Node(Thread.currentThread(), mode);
    //临时变量 假设tail(队尾=null)
    Node pred = tail;
    //队尾不等于null
    if (pred != null) {
        //当前节点的上一个节点是尾节点 插入到尾节点之后
        node.prev = pred;
        //采用cas将尾节点设置为当前节点
        if (compareAndSetTail(pred, node)) {
            //尾节点的下一个节点就是当前节点
            pred.next = node;
            //返回当前节点
            return node;
        }
    }
    //初始化第一个节点
    enq(node);
    //返回当前节点
    return node;

}

 

//初始化第一个节点
private Node enq(final Node node) {
    for (;;) {
        //将aqs尾节点临时存放
        Node t = tail;
        if (t == null) { // Must initialize
            //采用cas设置头节点 方便运算 头的线程永远是null 
            if (compareAndSetHead(new Node()))
            //头尾相连   //代码走完 继续循环
            tail = head;
        } else {

       //之前得尾节点赋值给当前得节点得上一个节点(也就是当前节点得上一个节点是之前得
       //尾节点
      node.prev = t;
      //采用cas设置尾节点
    if (compareAndSetTail(t, node)) {
         //当前节点就是尾节点
          t.next = node;
          //返回当前节点 for循环结束
          return t;
           }
        }
    }
}

 

//node:排队得尾节点
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        //中断标识
        boolean interrupted = false;
        for (;;) {
           //获取排队得尾节点的上一个节点
            final Node p = node.predecessor();
             //1.如果上一个节点是头节点 就是第一个排队 尝试去拿这把锁 
             //2.有可能当前正在拿锁的线程上一个节点持有锁的人已经执行完了 
             //并且释放了锁 而且当前节点刚好也拿
            //到锁 此时的头节点就变为了当前节点 并将上一个节点持有锁的人从aqs中移出
            if (p == head && tryAcquire(arg)) {
                //设置头节点为当前节点 
                setHead(node);
                //2.将之前的头节点的指向改为null 方便gc
                p.next = null; // help GC
                failed = false;
                 //返回中断状态 此时为false?  ??
                return interrupted;
            }
           //失败获取锁后要不要睡眠调用 目的是为了防止等待的线程去调用park()  
            if (shouldParkAfterFailedAcquire(p, node) &&
                //阻塞调用park()  此时当前线程处于阻塞状态 有可能在阻塞时
                //上一个拿到锁的执行完了 唤醒了此线程 这是中断状态将变为true
                parkAndCheckInterrupt())
                //设置中断状态为true  
                interrupted = true;
        }
    } finally {
       //待分析
        if (failed)
            cancelAcquire(node);
    }
}


 

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     //拿到当前节点的上一个节点的状态 waitStatus默认为0
    int ws = pred.waitStatus;
     //Node.SIGNAL=-1 如果上一个节点的状态为-1  也就是上一个节点处于等待拿锁 直接返回true
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
          //返回true
        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 {
       
      //将当前排队的尾节点的上一个节点的等待状态修改为-1 
      //每次是当前线程进来排队时将上一个节点的等待状态修改为-1
      //因为上一个节点处于等待阻塞状态 不能自己去修改自己 
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //返回flase
    return false;
}

 //失败获取锁后要不要睡眠调用park()睡眠   pred:当前队尾的上一个节点   node:当前队尾
 //将当前线程阻塞 并等待被唤醒
 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
 }

 

protected final boolean tryAcquire(int acquires) {
    //获得当前线程
    final Thread current = Thread.currentThread();
    //获取当前锁的状态 0未持有  1持有 这个属性在AbstractQueuedSynchronizer中
    int c = getState();
    if (c == 0) {
        //判断是都需要排队
        if (!hasQueuedPredecessors() &&
             //采用cas将当前aqs队列中state设置为1
            compareAndSetState(0, acquires)) {
            //将当前线程赋值设置为正在占用锁的线程
            setExclusiveOwnerThread(current);
            //拿到锁 并返回
            return true;
        }
    }
    //如果当前进来的这把锁跟占有这把锁的线程是相等 证明是同一把锁 可重入锁
    else if (current == getExclusiveOwnerThread()) {
         //可重入锁次数+1
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
         //将重入锁数量设置给aqs的status
        setState(nextc);
        return true;
    }
    return false;
}

 

   

 //判断当前线程是否需要排队
 public final boolean hasQueuedPredecessors() {
        //拿到尾节点
        Node t = tail; 
        //拿到头节点
        Node h = head;
        Node s;
        //第一次进来t跟h都为null 返回false 由于是!hasQueuedPredecessors()所以拿锁成功
        //如果有人排队判断头节点的下一个节点是否等于null 显然不等于null所以为false
        //由于是|| 所以进入下一个判断头节点的下一个节点线程是否不等于当前线程  这里保证每次拿锁
        //的线程是头节点的下一个节点 如果不等于 显然返回true 那么当前线程将要去排队 
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
 }
  •        总结: 尝试去拿锁 首先获取当前线程和当前锁的状态 如果是0  查看是否需要排队
  •        不需要排队则直接拿锁成功  并使用cas将当前锁的状态修改为1  并设置持有这把锁的线程 返回true
  •         还有一个判断就是:如果当前线程等于持有这把锁的线程 就是可重入锁 将锁的状态加1 
  •    

  •         需要排队则 去排队 将当前线程封装成Node插入aqs链表(第一个排队会创建一个线程为null的Node方便运算) 并返回 
  •         如果当前只有一个自己在排队(当前节点链表的上一个节点等于头节点)并且拿到锁(自旋拿锁)  则替换之前的头节点为
  •         当前节点 并将之前头节点置空 方便回收
  •        
  •         如果当前节点不是只有一个在排队 则去判断上一个节点的等待状态是否为-1 是-1则返回true 将当前线程调用park()阻塞
  •         上一个节点的状态等于0的话 采用cas将上一个线程的等待状态设置为-1 并返回fasle 再次尝试去拿锁(自旋拿锁) ...所以          一般   会尝试两次去拿锁  这里会一直循环 
  •         
  •         如果当前不止一个在排队 则直接判断上一个节点的等待状态是否为-1  是-1则返回true 将当前线程调用park()阻塞
  •         上一个节点的状态等于0的话 采用cas将上一个线程的等待状态设置为-1 并返回fasle 再次尝试去拿锁   这里会一直循环 
  •         释放锁的时候会将头节点的下一个节点等待状态为-1的唤醒 调用unpark() 唤醒之后就会走上面的cas+自旋+park
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
公平是一种多线程同步机制,它可以保证多个线程按照请求的顺序依次获得。在Java中,公平的实现类是ReentrantLock,下面我们来分析一下它的源码。 ReentrantLock的构造函数如下: ``` public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } ``` 可以看到,ReentrantLock的构造函数接受一个boolean类型的参数fair,用来指定该是否为公平。如果fair为true,则创建一个FairSync对象,否则创建一个NonfairSync对象。这两个对象都是ReentrantLock的内部类,它们分别实现了公平和非公平的逻辑。 FairSync的实现如下: ``` static final class FairSync extends Sync { final void lock() { acquire(1); } 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; } } ``` FairSync继承了Sync类,它实现了公平的逻辑。FairSync中的lock方法直接调用了acquire方法,acquire方法中会调用tryAcquire方法来尝试获取。tryAcquire方法首先判断当前的状态,如果状态为0,表示没有被占用,这时它会判断是否有等待队列中的线程,如果没有,则尝试获取。如果状态不为0,表示已经被占用,这时它会判断当前线程是否为的持有者,如果是,则直接增加的状态计数器,否则返回false表示获取失败。 NonfairSync的实现如下: ``` static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } ``` NonfairSync同样继承了Sync类,它实现了非公平的逻辑。NonfairSync中的lock方法首先尝试使用CAS操作来获取,如果成功则设置当前线程为的持有者,否则调用acquire方法。tryAcquire方法中调用了nonfairTryAcquire方法来尝试获取,这个方法与FairSync中的tryAcquire方法类似,只是它不会判断等待队列中的线程是否比当前线程更早请求。 总结一下,ReentrantLock实现了公平和非公平两种逻辑,它的内部类Sync、FairSync和NonfairSync分别实现了的基本逻辑,包括获取、释放等。在使用ReentrantLock时,我们可以选择使用公平或非公平,具体取决于我们的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值