AQS(一)(核心思想,独占模式ReentrantLock简单实现)

1、什么是AQS?

AQS的全称为(AbstractQueuedSynchronizer),即队列同步器,它是JUC包下面的核心组件,它的主要使用方式是继承,子类通过继承AQS,并实现它的抽象方法来管理同步状态,它分为独占锁和共享锁。很多同步组件都是基于它实现的,比如ReentrantLock就是基于AQS的独占锁实现,它表示每次只能有一个线程持有锁。再比如CountDownLatch、Semaphore等是基于AQS的共享锁实现的,它允许多个线程同时获取锁,并发的访问资源。AQS是建立在CAS上的一种FIFO的双向队列,通过维护一个使用volatile修饰的int类型的state表示资源的锁状态。

2、AQS实现的核心思想?

AQS的核心思想是:如果被请求的共享资源共享,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒是锁分配的机制,这个机制是AQS内部维护着一个FIFO的队列,即CLH队列。CLH队列是FIFO双端双向队列,实现公平锁。线程通过AQS获取锁失败,机会将线程封装成一个Node节点,插入队列尾;当有线程释放锁时,将队列中最先入队的节点拿出来去抢夺锁,然后这个节点出队。

Node节点的组成

static final class Node {
   /** 表示节点正处于共享模式下等待标记 */
   static final Node SHARED = new Node();
   /** 表示节点处于独占锁模式的等待标记 */
   static final Node EXCLUSIVE = null;
   /** waitStatus值,表示线程取消 */
   static final int CANCELLED =  1;
   /** waitStatus值,表示线程需要挂起 */
   static final int SIGNAL    = -1;
   /** waitStatus值,表示线程处于等待条件*/
   static final int CONDITION = -2;
   /**waitStatus值,表示下一个共享模式应该无条件传播*/
   static final int PROPAGATE = -3;
   /**状态字段*/
   volatile int waitStatus;
   /**前驱节点 */
   volatile Node prev;
   /**后继节点 */
   volatile Node next;
   /**当前线程*/
   volatile Thread thread;
   /**将此节点入列的线程,用来指向下一个节点*/
   Node nextWaiter;
  }

AbstractOwnableSynchronizer中的重要属性

AQS抽象类还继承了AbstractOwnableSynchronizer,这个抽象类也有几个关键属性。

   //表示当前持有锁的线程
    private transient Thread exclusiveOwnerThread;

AQS抽象类中的重要属性

   //head节点存储的是当前持有锁的线程,它的后继就是队列的头节点
   private transient volatile Node head;

   //指向队列的尾节点
    private transient volatile Node tail;

    //锁的状态
    private volatile int state;

    //获取当前锁的状态
    protected final int getState() {
        return state;
    }
   //修改锁的状态
    protected final void setState(int newState) {
        state = newState;
    }

    //通过CAS方式设置state的值,并发环境下使用
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQS重要方法

AQS中使用了模板方法模式,自定义同步器需要重写下面的几个AQS提供的模板方法。

isHeldExclusively()//该线程是否处于独占资源。只有用到condition才需要实现它.
tryAcquire(int)//独占方式获取资源,成功返回true,失败返回false
tryRelease(int)//独占方式释放资源,成功返回true,失败返回false
tryAcquireShared(int)//共享方式获取资源。负数表示失败,0表示成功但是没有剩余可用资源;正数表示成功且有剩余资源
tryReleaseShared(int)//共享方式释放资源.成功返回true,失败返回false.

3、迷你版公平锁ReentrantLock的实现

package lock;
import MiniLock.Lock;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;
public class MiniReentrantLock implements Lock {
    /*
    * 加锁锁的就是资源 用state来表示
    * 0表示未加锁 >0表示加锁
    * */
    private volatile int state;//使用volatile保证可见性
    /*
    *独占模式:同一时刻只有一个线程可以持有锁,其他线程在未获取到锁时会阻塞
    * */

    //表示当前独占锁的线程
    private Thread exclusiveOwnerThread;

    /*
    * 需要有两个引用去维护阻塞队列
    * 1、Head 指向队列的头节点
    * 2、Tail 指向队列的尾节点
    * */
    //比较特殊:head节点对应的线程就是当前占用锁的线程
    private Node head;
    private Node tail;

    /*
    * 阻塞的线程被封装为Node节点,然后放入到FIFO队列
    * */
    static final class Node{
        //前置节点引用
        Node prev;
        //后置节点引用
        Node next;
        //封装的线程
        Thread thread;
        public Node(Thread thread) {
            this.thread = thread;
        }
        public Node() {
        }
    }
    /*
    * 加锁
    * 模拟的是公平锁
    *
    * lock的过程是什么样的?
    * 情景1:线程进来后发现,当前state=0,这个时候就直接抢锁
    * 情景2:线程进来后发现,当前state>0,这个是时候就需要将当前线程入队
    * */
    @Override
    public void lock() {
        //第一次获取到锁时,将state设置为1
        //第n次重入时,将state设置为n
        acquire(1);
    }
    /*
    * 竞争资源
    * 1、尝试获取锁,获取成功并占用
    * 2、抢占锁失败,阻塞当前线程
    * */
    private void acquire(int arg){
        if(!tryAcquire(arg)){
            //尝试获取锁失败:1、当前线程入队 2、当期线程一直自旋尝试获取锁,直到成功获取到锁
            //1、入队
            Node node = addWaiter();
            //2、当前当前线程尝试抢占锁
            acquireQueud(node,arg);
        }
    }
    /*
    * 第一次尝试获取锁失败后,入队,然后需要一直自旋尝试获取锁,直到获取成功
    * */
    private void acquireQueud(Node node,int arg){
        //只要获取锁成功,才会退出自旋
        for (;;){
            //什么情况下,当前node被唤醒之后可以尝试去获取呢?
            //只有一种情况,当前node是head的后继节点,次啊能被唤醒之后去获取
            //先来后到的一种策略
            Node pred=node.prev;
            //pred==head具有抢占权限
            if(pred==head&&tryAcquire(arg)){
                //抢占成功,说明当前线程竞争锁成功

                //1.设置队列的head为当前线程的node
                setHead(node);
                //2、当前线程出队,help gc
                pred.next=null;
                return;
            }
            System.out.println("线程:"+Thread.currentThread().getName()+",挂起");
            //将当前线程挂起
            LockSupport.park();
            System.out.println("线程:"+Thread.currentThread().getName()+",唤醒");
            //当锁被释放时候在,就会唤醒线程
        }
    }
    /*
    * 抢占锁失败之后的逻辑是什么?
    * 1、需要将当前线程封装为node,加入到阻塞队列
    * 2、需要将当前线程park掉,使线程处于挂起状态。
    *
    * 唤醒线程后该怎么操作?
    * 1、检查当前node节点是否为head.next节点(head.next节点是拥有抢占权限的线程,其他的node都没有抢占的权限)
    * 2、进行抢占锁
    *   成功:1、将当前node设置为head,将老的head出队
    *   失败:2、继续park,等待被唤醒
    * */

    /*
    * 当前线程入队,返回当前线程成对应的Node节点
    * */
    //addWaiter()执行完成之后,保证当前线程入队成功
    public Node addWaiter(){
        Node newNode = new Node(Thread.currentThread());
        /*入队步骤
        * 1、找到newNode的前置节点pred
        * 2、newNode.prev=pred;
        * 3、CAS更新tail为newNode
        * 4、pred.next=newNode;
        * */
        //前置条件,队列已经有等待者node了,当前node不是第一个入队的node
        Node pred=tail;
        if(pred!=null){
            newNode.prev=pred;
            //条件成立 说明当前线程成功入队
            if(compareAndSetTail(pred,newNode)){
                pred.next=newNode;
                return newNode;
            }
        }
        //执行到这里的情况
        //1、tail==null 队列为空
        //2、CAS设置newHead为tail时失败,被其他线程抢先了
        enq(newNode);
        return newNode;
    }

    /*
    * 自旋入队 只有成功后才返回
    * 队列为空或者入队失败 需要一直自旋入队直到成功
    * */
    private void enq(Node node){
        for(;;){
            //第一种情况:队列为空 说明当前线程是第一个抢占锁失败的线程
            if(tail==null){
                //条件成立,说明当前线程给当前持有锁的线程补充head操作成功
                if(compareAndSetHead(new Node())){
                    tail=head;
                }
            }else{
                //说明当前队列中已经有元素了,这里是一个追加node的过程
                Node pred=tail;
                if(pred!=null){
                    node.prev=pred;
                    if(compareAndSetTail(pred,node)){
                        pred.next=node;
                        //入队成功 一定要return
                        return;
                    }
                }
            }
        }
    }
    /*
    * 尝试获取锁
    * true:获取成功
    * false:抢占失败
    * */
    private boolean tryAcquire(int arg){
        if(state==0){
            /*state=0时就可以直接抢占锁吗?不可以因为是公平锁,要遵循先来后到*/
            //当前线程前面没有等待的线程且更改state值成功
            if(!hasQueuedProcessor()&&compareAndSetState(0,arg)){
                //抢锁成功
                this.exclusiveOwnerThread=Thread.currentThread();
                return true;
            }
         //当前锁被占用的时候就会执行这个条件

         //条件成立:说明当前线程就是持锁线程
        }else if (Thread.currentThread()==this.exclusiveOwnerThread){
            //这里面不存在并发,只有当前加锁的线程,才能对state修改
            //锁重入的过程
            int c=getState();
            c=c+arg;
            this.state=c;
            return true;
        }
        //返回false的条件
        //1、CAS失败
        //2、当前线程不是占有锁的线程
        return false;
    }
    //释放锁
    @Override
    public void unlock() {
        release(1);
    }
    private void release(int arg){
        //条件成立:说明线程已经完全释放锁了
        //这个时候就需要唤醒阻塞队列中等待的线程
        if(tryRelease(arg)){
            Node head=this.head;
            //先要判断有没有等待者,
            if(head.next!=null){
                //说明有等待者,公平锁唤醒head.next节点
                unparkSuccessor(head);
            }
        }
    }
    private void unparkSuccessor(Node node){
        Node s=node.next;
        if(s!=null&&s.thread!=null){
            LockSupport.unpark(s.thread);
        }
    }
    /*
    * 完全释放锁成功,则返回true
    * 否则说明state>0,返回false
    * */
    private boolean tryRelease(int arg) {
        int c = getState() - arg;
        //如果
        if (getExclusiveOwnerThread() != Thread.currentThread()) {
            throw new RuntimeException("fuck you! must getLock!");
        }
        //不存在并发问题,同一时刻能进入到这里的线程只有一个就是exclusiveOwnerThread

        //条件成立:说明当前线程持有的锁已经完全释放了
        if(c==0){
            //1.把exclusiveOwnerThread置为null
            this.exclusiveOwnerThread=null;
            //2、state=0
            this.state=c;
            return true;
        }
        this.state=c;
        return false;
    }
    /*
    *判断阻塞队列中当前线程的前面是否有等待的线程
    * true:表示有
    * false:表示没有,当前线程可以尝试抢占
    *
    * 返回false的情况?
    * 1、当前队列为空
    * 2、当前线程为head.next节点
    * */
    private boolean hasQueuedProcessor(){
        Node h=head;
        Node t=tail;
        Node s;
        //条件1:h!=t
            //成立:说明队列已经有node了
            //不成立:1.h=t=null 2、h=t=head,

        //条件2:(s=h.next)==null||s.thread!=Thread.currentThread()
           //条件2.1:已经有了head.next节点,其他线程再来时需要返回true
           //条件2.2:成立:说明当前线程不是h.next节点对应的线程
                  //不成立:说明当前线程,就是h.next节点对应的线程,需要返回false,线程可以去竞争锁
        return h!=t&&((s=h.next)==null||s.thread!=Thread.currentThread());
    }


    private void setHead(Node node){
        this.head=node;
        //因为当前node已经是获取成功的线程了,所以队里它的前置设为null,
        node.thread=null;
        node.prev=null;
    }

    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
    public Node getHead() {
        return head;
    }
    public Node getTail() {
        return tail;
    }


    private static final Unsafe unsafe;
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);

            stateOffset = unsafe.objectFieldOffset
                    (MiniReentrantLock.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                    (MiniReentrantLock.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                    (MiniReentrantLock.class.getDeclaredField("tail"));

        } catch (Exception ex) { throw new Error(ex); }
    }

    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
}

4、以三个线程来模拟ReentrantLock的公平锁的抢占锁的过程

1、线程1调用reentrantLock.lock()会发生什么?
在这里插入图片描述

线程1调用lock方法后,发现state=0,并且发现队列为空,则更新state的值,并将线程1设置为持有锁的线程。

2、线程2进入调用reentrantLock.lock()会发生什么?

  • 第一步:

在这里插入图片描述

发现state不为0并且线程2不是加锁的线程,说明当前锁已被被的线程持有,然后判断队列是不是为空,如果为空:将head节点设置为当前持有锁的线程。

  • 第二步

在这里插入图片描述

然后线程2入队,让tail指向线程2的节点,线程2的节点的前驱设置为head,head节点的后继设置为线程2的节点。并且线程2的节点会一直自旋尝试去抢占锁。

3、线程3进入调用reentrantLock.lock()会发生什么?

在这里插入图片描述

线程3发现锁已被其他线程抢占,并且当前持有锁的线程不是线程3,队列也不为空,则线程3的节点入队,让线程2节点的后继指向线程3的节点,线程3的节点的前驱指向线程2的节点,tail指向线程3的节点。并且发现head的后继不是线程3的节点,则线程3进入休眠状态。注意:此时线程2的节点一直在尝试竞争锁。

当线程2抢占锁成功后,出队,head存储线程2节点,head的后继指向线程3的节点,这时才唤醒线程3,线程3自旋进行尝试抢占锁。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值