AQS的知识详解

1.先谈谈什么是AQS:其实本质上AQS就是一个类,全称AbstractQueuedSynchronizer

在这个类中,有几个属性(主要有一个属性private volatile int state)和一个双向队列(也称CLH队列),属于并发包(JUC)下的一个基类

很多并发包下的类都是基于AQS实现的,比如:CountDownLatch,Semaphore,ReentrantLock等

先来看看AbstractQueuedSynchronizer这个类的结构吧

 

 先来看一下Node这个静态内部类,对一些属性进行简单的介绍

static final class Node {
	//这是排他锁的一个标志
    static final Node EXCLUSIVE = null;
	//如果带有这个属性,证明是失效了
    static final int CANCELLED =  1;
	//具有这个标识,证明后继节点要被唤醒
    static final int SIGNAL    = -1;
    //Node对象存储标识的地方
    volatile int waitStatus;
    //指向前一个节点
    volatile Node prev;
    //指向后一个节点
    volatile Node next;
    //当前Node绑定的线程
    volatile Thread thread;
    //返回前驱节点,如果前驱节点为null,则抛出NullPointerException
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
}

这里简单介绍一下volatile关键字吧

主要有3大特点:

1)可以保证可见性(就是一个线程修改完值,另外线程可以知道)

2)不能保证原子性(就是有线程安全问题)

3)禁止指令重排(就是操作代码的指令是固定的,这里涉及到了计算机底层,了解就行)

2。来看看AQS的大概的结构吧

图大概是这样的:

 

其实AQS内部还做了一些工作,会创建出一个哨兵节点,放到第一个位置,然后才和其它节点相连

哨兵节点的作用:1)监控之后节点的状态,2)保证了线程安全

3.我们来开始分析源码吧

因为很多类都是基于AQS实现的,那我们就从ReentrantLock入手吧

1)首先加锁

当我们调用加锁方法时

其实调用了

 

 这个sync其实是ReentrantLock的一个属性,sync分为公平和非公平

这个类继承了AQS

ReentrantLock默认是非公平锁,我们先从非公平锁入手

 

final void lock() {
	//采用CAS发方式尝试将state的状态从0改为1,如果返回true,则修改成功(不知道CAS大家可以去了解一下)	
    if (compareAndSetState(0, 1))
    	//将一个属性设为当前线程,而这个属性是AQS的父类提供的
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

2)查看acquire方法

public final void acquire(int arg) {
	//tryAcquire再次尝试获取锁资源,如果尝试成功,则返回true
    if (!tryAcquire(arg) &&、
    	//尝试获取锁失败,将当前线程封装成Node对象,追加到AQS队列中
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //线程中断
        selfInterrupt();
}

3)查看tryAcquire方法

 点进去发现,直接抛出了一个异常,说明这是一个模板方法

找到它的实现

 进入NonfairSync中

final boolean nonfairTryAcquire(int acquires) {
	//获取当前线程
    final Thread current = Thread.currentThread();
    //获取AQS中的state
    int c = getState();
    //如果state为0,则再次尝试获取锁资源
    if (c == 0) {
    	//这行代码似曾相识,采用CAS发方式尝试将state的状态从0改为1,如果返回true,则修改成功
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //占有锁资源的线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
        //将state加1
        int nextc = c + acquires;
        //如果加1后,小于0,则超出可重入锁的最大范围,抛出Error
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //没问题,就对state进行赋值
        setState(nextc);
        return true;
    }
    return false;
}

查看addWaiter方法

//走到这里说明前面获取锁资源失败
private Node addWaiter(Node mode) {
	//创建Node类,并设置thread为当前线程,设置为排他锁,mode就是传过来的Node.EXCLUSIVE
    Node node = new Node(Thread.currentThread(), mode);
    //获取AQS队列中的尾部节点
    Node pred = tail;
    //如果tail不为null,说明队列中有其它线程
    if (pred != null) {
    	//当前节点的prev设置当前节点(就是新创建的node节点)为尾节点
        node.prev = pred;
        //基于CAS的方式,将tail节点设置为当前节点
        if (compareAndSetTail(pred, node)) {
          //将之前为next节点的next,设置为当前节点
            pred.next = node;
            return node;
        }
    }
    //进入enq方法,在下面
    enq(node);
    return node;
}
---------------------------------------------------------------
    //走到这里说明队列为空,或者CAS失败
    private Node enq(final Node node) {
        for (;;) {
        //重新获取当前的tail节点为t
            Node t = tail;
            
            if (t == null) { // Must initialize
            //现在没人排队,都是空
                if (compareAndSetHead(new Node()))
                //这里创建了一个新的节点作为头节点,这个head没有意义(相对于前面说的哨兵节点)
                //将头尾都指向了这个node节点
                    tail = head;
            } else {
            //有人排队,往队列尾巴里塞
                node.prev = t;
                //基于CAS的方式,将tail节点设置为当前节点
                if (compareAndSetTail(t, node)) {
                //将之前为next节点的next,设置为当前节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

查看 acquireQueued方法

//已经将node节点加入的当前的对列中,执行一下方法
final boolean acquireQueued(final Node node, int arg) {
	//标识!
    boolean failed = true;
    try {
    //标识!
        boolean interrupted = false;
        for (;;) {
        	//获取当前节点的前驱节点p
            final Node p = node.predecessor(); //这里出现异常的概率几乎为0
            //如果p是head,再次尝试获取锁资源(将state从0变为1,或者重入锁操作)
            if (p == head && tryAcquire(arg)) {
            	//设置head节点为当前节点,并且将thread,pred都设置为null,因为拿到锁,就不需要排队了
            	//这时当前的节点就变成的哨兵节点,而之前的哨兵节点等待gc回收
                setHead(node);
                p.next = null; // help GC,加快回收
                failed = false; //将标识修改为false
                return interrupted;//返回interrupted
            }
            //保证上一个节点是-1,才会返回true,将线程阻塞,等待唤醒锁资源
            if (shouldParkAfterFailedAcquire(p, node) &&
                //基于Unsafe类的par方法,挂起线程
                parkAndCheckInterrupt())//这里是可能出现异常的地方,JVM内部出现问题时,所以finally代码块中指向的概率																										为0
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
------------------------------shouldParkAfterFailedAcquire方法------------------------------
    //node是当前节点,pred是上一个节点
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    	//获取上一个节点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
        	//如果上一个节点的状态为SIGNAL,说明一切正常
            return true;
        //如果上一个节点已经失效
        if (ws > 0) {
            do {
            	//将当前节点的pred指针指向上一个的上一个
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);//一直找到小于等于0的
            //将重新标识的最近的有效节点的next
            pred.next = node;
        } else {
     	    //小于等于0,不等于-1,将上一个有效节点的状态改为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

接下来释放锁:这期太长了,下期见!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值