JUC并发包下的AQS锁源码解析(一)

JUC并发包下的AQS锁源码解析

AQS的定义

谈到并发就不得不说AQS(AbstractQueuedSynchronizer)所谓的AQS翻译过来就是抽象队列同步器,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量来表示同步状态,通过CLH对列来完成资源获取线程的排队工作。说白了就是 采用了volatile+CAS来保证加锁解锁,用队列来完成线程的排队,用park/unpark来实现线程的等待与唤醒

同步器的实现

同步器主要是通过继承的方式来实现,AQS采用模板方式,让子类来继承同步器并实现它的抽象方法来管理同步状态。
主要是
**getState()**获取当前状态,用来判断是否有线程加锁了。
setState改变state状态,1为加锁,0是代表该锁没有被线程占有。
**compareAndSetState(int expect,int update)**进行CAS操作保证原子性

ReentrantLock实现AQS

我们熟知的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS来实现的。
现在我们就来聊聊ReentrantLock底层是如何实现AQS的
ReentrantLock里分公平锁跟非公平说,我们先来说公平锁

//java\util\concurrent\locks\ReentrantLock.java
public void lock() {
       sync.lock();
   }
   abstract void lock();
   //选择公平锁的实现lock()方法
    final void lock() {
           acquire(1);
       }
    //java\util\concurrent\locks\AbstractQueuedSynchronizer.java
       public final void acquire(int arg) {
       //tryAcquire是尝试去获取锁,如果成功了则放回true,失败了则返回false
       //当获取锁失败则表示该锁已经被其他线程占有,该线程就要进入等待队列等待
       //addWaiter添加等待node节点,
       //添加好node节点放入acquireQueued()队列中,然后让该线程park
       if (!tryAcquire(arg) &&
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
   }

解析AQS怎么尝试获取锁的(公平锁)

java\util\concurrent\locks\ReentrantLock.java

/**
        * Fair version of tryAcquire.  Don't grant access unless
        *  tryAcquire的公平版。不要授予访问权限,除非
        * recursive call or no waiters or is first.
        * 递归调用或者没有等待者或者是第一个。
        */
       protected final boolean tryAcquire(int acquires) {
      		 //获取当前线程
           final Thread current = Thread.currentThread();
           //获取同步锁的状态
           int c = getState();
           //判断同步锁的状态
           if (c == 0) {
           //如果同步锁的状态为0表示当前同步锁没有被任何线程所拥有
               if (!hasQueuedPredecessors() &&
                   compareAndSetState(0, acquires)) {
                   //因为是公平锁就算该同步锁的状态为0也不可以直接抢占锁。
                   //还要判断等待队列里是否有正在排队的线程,如果有,当前线程就不可以进入抢锁过程,要去队列里排队等待。假如队列里没有排队的线程,那就可以给线程上锁
                   setExclusiveOwnerThread(current);
                   return true;
               }
           }
           //判断当前是否为占有锁的线程,如果是则重入
           //lock跟sync一样都是可重入锁
           else if (current == getExclusiveOwnerThread()) {
               int nextc = c + acquires;
               if (nextc < 0)
                   throw new Error("Maximum lock count exceeded");
               setState(nextc);
               return true;
           }
           return false;
       }
   }

接着谈谈非公平锁是怎么实现的

解析AQS怎么尝试获取锁的(非公平锁)

/**
       * Performs lock.  Try immediate barge, backing up to normal
       * 执行锁定。尝试立即插入,恢复正常
       * acquire on failure.
       * 失败时获取。
       */
      final void lock() {
      //非公平锁一上来就尝试去获取锁,如果获取到了就占有该锁,不管等待队列里是否有在排队的线程
          if (compareAndSetState(0, 1))
              setExclusiveOwnerThread(Thread.currentThread());
          else
          //如果抢锁失败则进入acquire方法,就跟公平锁差不多了
              acquire(1);
      }
      

公平锁与非公平锁的区别:
非公平锁:一上来就不管三七二十一,先抢锁,不管是否有在排队的线程
公平锁:老的线程排队使用锁,新线程仍然排队使用锁

获取锁失败后进入等待队列

java\util\concurrent\locks\AbstractQueuedSynchronizer.java

/**
   * Creates and enqueues node for current thread and given mode.
   * 为当前线程和给定模式创建节点并将其排队。
   * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for 	shared
   * @return the new node
   */
   //添加一个等待队列,将lock维护的node节点传入,Node是一个双向链表
  private Node addWaiter(Node mode) {
  //将当前线程存入该node中
      Node node = new Node(Thread.currentThread(), mode);
      // Try the fast path of enq; backup to full enq on failure
      //将队列中的尾节点赋值给pred
      Node pred = tail;
         //如果尾节点为空的情况,说明当前队列中没有等待的线程
      if (pred != null) {
			//将当前节点插入队列的尾节点
          node.prev = pred;
          if (compareAndSetTail(pred, node)) {
              pred.next = node;
              return node;
          }
      }
      //如果为空
      enq(node);
      return node;
  }
  /**
   * Inserts node into queue, initializing if necessary. See picture above.
   * 将节点插入队列,必要时进行初始化
   * @param node the node to insert
   * @return node's predecessor
   */
  private Node enq(final Node node) {
  //死循环,直到节点添加成功
      for (;;) {
          Node t = tail;
          //第一遍循环时tail指针为空,进入if逻辑,使用CAS操作设置head指针,将head指向一个新创建的Node节点
          if (t == null) { // Must initialize
              if (compareAndSetHead(new Node()))
                  tail = head;
          } else {
          //保证头节点为空,将等待线程设置为尾节点
              node.prev = t;
              if (compareAndSetTail(t, node)) {
                  t.next = node;
                  return t;
              }
          }
      }
  }

将添加好的节点存入队列中

/**
    * Acquires in exclusive uninterruptible mode for thread already in
    * 以独占不间断模式获取已在的线程
    * queue. Used by condition wait methods as well as acquire.
    * 排队。由条件等待方法和获取方法使用。
    *
    * @param node the node
    * @param arg the acquire argument
    * @return {@code true} if interrupted while waiting
    */
   final boolean acquireQueued(final Node node, int arg) {
       boolean failed = true;
       try {
           boolean interrupted = false;
           //死循环,直到将node插入队列中并且让其park,或者获取到锁退出循环
           for (;;) {
               final Node p = node.predecessor();
               //判断node节点是否为头节点,如果是,则表示该节点是队列中第一个排队的,就可以尝试去获取锁
               if (p == head && tryAcquire(arg)) {
               //如果获取锁成功了,将node节点设置为head
                   setHead(node);
                   //将该node清空,方便GC回收
                   p.next = null; // help GC
                   failed = false;
                   return interrupted;
               }
               //如果不是头节点,且获取锁失败了
               //就会通过shouldParkAfterFailedAcquire方法 将head节点的waitStatus变为了SIGNAL=-1,
               //则将该线程插入等待队列中,并且把它park阻塞住
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   interrupted = true;
           }
       } finally {
           if (failed)
               cancelAcquire(node);
       }
   }

以上就是公平锁跟非公平锁的加锁过程
接下来谈谈解锁过程

public void unlock() {
       sync.release(1);
   }
 
   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;
   }
   //尝试释放锁
   protected final boolean tryRelease(int releases) {
   //获取当前锁状态,将其减一
           int c = getState() - releases;
           //判断该线程是否未拥有锁的线程
           if (Thread.currentThread() != getExclusiveOwnerThread())
               throw new IllegalMonitorStateException();
           boolean free = false;
           //如果state状态为零则表示该锁可以释放
           if (c == 0) {
               free = true;
               将锁标志线程清空
               setExclusiveOwnerThread(null);
           }
           //如果不是,则将该state减一后重新赋值
           setState(c);
           return free;
       }

总结:
在获取同步状态时,同步器维护⼀个同步队列,获取状态失败的线程都会被加⼊到队列中并在队列中进⾏⾃旋;移出队列(或停⽌⾃旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调⽤tryRelease(int arg)⽅法释放同步状态,然后唤醒头节点的后继节点。《java并发艺术》

Condition的源码解析

condition中定义了等待/通知两种类型的方法,当线程调用这些方法时。需要获取到Condtion对象关联的锁。Condition对象是由Lock对象new 出来的,Condtion是依附Lock。就跟wait/notify依赖于sync锁是一样的。
condition与wait/notify的去区别
condition比wait/notify跟加灵活,他可以指定线程等待与通知
Condition的使⽤⽅式⽐较简单,需要注意在调⽤⽅法前获取锁,
condition中依靠await跟signal这两个方法来实现的。
底层依旧靠AQS来实现。
ConditionObject类是AQS的内部类,因为condition的实现需要依靠lock锁,所以再内部也是很合理的,每个condition中都有自己维护的队列。该队列是实现等待/通知的关键。

condition中的await源码

 public final void await() throws InterruptedException {
           if (Thread.interrupted())
               throw new InterruptedException();
            //将当前线程放入condition中的队列中
           Node node = addConditionWaiter();
            //获取当前锁状态,调用了await要释放当前锁,让其他线程去尝试获取锁
           long savedState = fullyRelease(node);
           int interruptMode = 0;
           //isOnSyncQueue判断当前线程是否为condition的头节点,如果是让该线程park,让出cpu执行权,给其他线程竞争获取锁
           while (!isOnSyncQueue(node)) {
               LockSupport.park(this);
               if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                   break;
           }
           //调用signal后唤醒该等待线程后,让该线程重新进入获取锁的状态
           //进入获取锁的等待队列,竞争获取锁的执行权
           if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
               interruptMode = REINTERRUPT;
           if (node.nextWaiter != null) // clean up if cancelled
               unlinkCancelledWaiters();
           if (interruptMode != 0)
               reportInterruptAfterWait(interruptMode);
       }
//执行await方法首先先在addConditionWaiter中将当前线程添加进condition队列中
       private Node addConditionWaiter() {
      //这个队列的实现方式跟AQS队列差不多
           Node t = lastWaiter;
           // If lastWaiter is cancelled, clean out.
           //判断该队列是否为空
           if (t != null && t.waitStatus != Node.CONDITION) {
               unlinkCancelledWaiters();
               t = lastWaiter;
           }
           Node node = new Node(Thread.currentThread(), Node.CONDITION);
           if (t == null)
               firstWaiter = node;
           else
               t.nextWaiter = node;
           lastWaiter = node;
           return node;
       }
   //获取当前锁状态,调用了await要释放当前锁,让其他线程去尝试获取锁
final long fullyRelease(Node node) {
       boolean failed = true;
       try {
           long savedState = getState();
           if (release(savedState)) {
               failed = false;
               return savedState;
           } else {
               throw new IllegalMonitorStateException();
           }
       } finally {
           if (failed)
               node.waitStatus = Node.CANCELLED;
       }
   }

condition中的signal方法

public final void signal() {
//先判断当前线程是否为获取锁的线程,如果不是则直接抛出异常。接着调用doSignal()方法来唤醒线程
   if (!isHeldExclusively())
       throw new IllegalMonitorStateException();
   Node first = firstWaiter;
   if (first != null)
       doSignal(first);
}
private void doSignal(Node first) {
   do {
       if ( (firstWaiter = first.nextWaiter) == null)
           lastWaiter = null;
       first.nextWaiter = null;
   } while (!transferForSignal(first) &&
            (first = firstWaiter) != null);
}
//这里先从transferForSignal()方法来看,通过上面的分析我们知道
//Condition队列中只有线程一创建的一个Node节点,且waitStatue为
//CONDITION,先通过CAS修改当前节点waitStatus为0,然后执行
//enq()方法将当前线程加入到等待队列中,并返回当前线程的前置节点
final boolean transferForSignal(Node node) {
   if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
       return false;

   Node p = enq(node);
   int ws = p.waitStatus;
   if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
       LockSupport.unpark(node.thread);
   return true;
}


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;
           }
       }
   }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值