AQS与CAS

AQS全名为AbstractQueuedSynchronizer:抽象的同步队列
CAS全名为CompareAndSet:比较与设置原子操作

今天咱们以ReentrantLock为例来讲解AQS与CAS

首先咱们看下ReentrantLock类图

这里包括两个部分

ReentrantLock类图

从类图可以看出,ReentrantLock实现了Lock接口

//Lock接口中定义这这么多方法,咱们重点看下lock()与unlock()方法
lock() //获取锁
lockInterruptibly()
tryLock()
unlock()//释放锁
newCondition()
//ReentrantLock 有三个内部类
	1abstract static class Sync extends AbstractQueuedSynchronizer
	2static final class FairSync extends Sync
	3static final class NonfairSync extends Sync

在这里插入图片描述

Sync类图

从类图出可以看出Sync同步类有两个子类,

Sync继承了抽象类AbstractQueuedSynchronizer,也就是Sync与它的子类都是同步器。
FairSync:公平同步器,又称公平锁。
NonfairSync:非公平同步器,又称非公平锁。

在这里插入图片描述

AbstractQueuedSynchronizer

了解AbstractQueuedSynchronizer首先需要了解下它的属性及内部类

//首先先了解下Node这个类,它是队列对应的Node节点
static final class Node {
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;
    //waitStatus值为1时表示该线程节点已释放(超时、中断),已取消的节点不会再阻塞
    static final int CANCELLED =  1;
    //waitStatus为-1时表示该线程的后续线程需要阻塞,即只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程
    static final int SIGNAL    = -1;
    //waitStatus为-2时,表示该线程在condition队列中阻塞(Condition有使用)
    static final int CONDITION = -2;
    //waitStatus为-3时,表示该线程以及后续线程进行无条件传播(CountDownLatch中有使用)共享模式下, PROPAGATE 状态的线程处于可运行状态
    static final int PROPAGATE = -3;
    volatile int waitStatus;
    volatile Node prev; //前置节点
    volatile Node next; //后置节点
    volatile Thread thread;
    Node nextWaiter;
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
}
private transient volatile Node head; //队列头节点
private transient volatile Node tail; //队列尾节点
private volatile int state; //同步状态
private transient Thread exclusiveOwnerThread; //执行权限线程
//咱们这里稍微重复下 volatile这个关键字
//这个关键子解决了两个问题 1、可见性 2、有序性
//可见性是指多个线程共享同一个变量,如果有线程修改了这个变量修改这个属性,另一个线程是否可以同步刷新到别的线程可见。
private static final Unsafe unsafe = Unsafe.getUnsafe(); //直接操作内存辅助类

ReentrantLock构造方法

//默认是非公平锁
public ReentrantLock() {
   sync = new NonfairSync();
}
//可以传个参数进来标志是公平锁还是非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock.lock()方法

咱们这次以非公平锁为例NonfairSync

//获取锁
final void lock() {
	//开始先尝试进行CAS操作
    if (compareAndSetState(0, 1))
    	//如果设置成功,同时设置AQS被当前线程占用
        setExclusiveOwnerThread(Thread.currentThread());
    else
    	//获取失败重新再次获取
        acquire(1);
}

AbstractQueuedSynchronizer.acquire(1)方法及重新获取

//尝试获取
public final void acquire(int arg) {
	//首先咱们梳理下方法的执行顺序
	//1、tryAcquire 2、addWaiter(Node.EXCLUSIVE) 3、acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
	//tryAcquire(arg) 获取资源,获取到返回true,获取不到返回false。这里只有两种情况下能获取到 1、AQS状态为0,既未被其他线程持有 2、当前线程已经持有
	//如果tryAcquire未获取资源会进行加入队列,让虚拟机调度
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    	//把已经加入队列的线程休息一会
        selfInterrupt();
}

Sync.tryAcquire(1):咱们点进去发现这个方法是一个空的实现,但是呢,Sync这个子类一个实现了,那么接下来咱们继续了解下这个具体实现

//非公平锁尝试获取状态
final boolean nonfairTryAcquire(int acquires) {
	//获取到当前线程
    final Thread current = Thread.currentThread();
    //获取AQS的状态 初始值是0
    int c = getState();
    //如果AQS未被占用
    if (c == 0) {
    	//进行CAS操作(比较并交换)
        if (compareAndSetState(0, acquires)) {
        	//获取成功设置当前线程权限为独占
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //判断当前线程是否是独占线程,这里小伙伴们就有疑问了,为什么要这么做呢?答案是可重入锁
    //什么是可重入锁呢,举个例子,一个线程调用了一个方法,这个方法中有lock.lock()获取锁,这个代码块中呢又调用了lock.lock()获取锁,那么第二次就可以直接在这里直接拿到权限
    else if (current == getExclusiveOwnerThread()) {
    	//从这里可以看出,同一个线程多次调用加锁方法后肯定会执行当前代码
        int nextc = c + acquires;
        //这里做了一次验证,就是验证咱们代码里是否是一次加锁多次解锁的操作
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // state = newState; state设置完成后会直接同步到主存,主存发现其他线程持有这个对象,那么就会通知并修改state(MESI,有兴趣的可以看下JVM内存管理篇文档)
        setState(nextc);
        return true;
    }
    return false;
}

咱们接着了解下AQS.addWaiter(Node.EXCLUSIVE)方法

//这里mode为null
private Node addWaiter(Node mode) {
	//创建一个Node节点
    Node node = new Node(Thread.currentThread(), mode);
    //把尾节点赋值给pred
    Node pred = tail;
    //如果有尾节点
    if (pred != null) {
    	//设置当前节点的前置节点为tail
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
        	//如果加入尾节点成功设置尾节点的下一个节点为当前node节点
            pred.next = node;
            //返回新加入的节点
            return node;
        }
    }
    //如果尾节点为null或者设置失败,继续将node插入节点
    enq(node);
    return node;
}

咱们继续看addWaiter方法中调用的AQS.enq()方法

//这个方法就解决了一个问题,把node节点插入到尾部并返回设置前的尾节点,由于是CAS操作必须进行循环
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 必须进行初始化
        	//如果尾节点为null,先对头节点设置为新创建的node
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

那么咱们接着看下AQS.acquireQueued(tail,arg)获取资源

//前面讲了,这个node节点就是新node插入队列后的前置节点
//注意,前置节点不一定是尾节点(因为方法执行到此方法有可能涉及到线程切换)
//从源码逻辑可以看出多线程去竞争资源,谁抢到谁有执行权(非公平锁)
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	//获取node的前置节点,如果为null直接抛出异常
        	//这里会循环获取node的前置节点
            final Node p = node.predecessor();
            //enq方法可以看出,head就是一个空的node节点,它的next就是为null
            //p == head的时候说明已经实例化了,再次调用nonfairTryAcquire方法尝试获取占用状态(当前线程持有)
            if (p == head && tryAcquire(arg)) {
            	//重新尾节点设置为头节点 
                setHead(node);
                p.next = null; // help GC 头节点的下一个节点为null (空头),就是当前线程已经获取到了资源,把前一个对象释放
                failed = false;
                return interrupted;
            }
            //shouldParkAfterFailedAcquire(p, node) 根据前置节点判断是否阻塞当前线程
            //parkAndCheckInterrupt() 阻塞当前线程并返回被打断状态
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
    	//获取资源失败后,重新释放资源
        if (failed)
            cancelAcquire(node);
    }
}
//根据前置节点判断是否阻塞当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		//获取前置节点waitStatus
        int ws = pred.waitStatus;
        //如果前置节点waitStatus为-1,前置节点已经释放资源, 直接返回true
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
        	//如果前置节点的状态大于0了 一只向前找,直到找到小于等于0的那个节点 直到
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	//前置节点前置节点的状态为-1 ,便于shouldParkAfterFailedAcquire返回true
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

ReentrantLock.unlock()释放资源

public void unlock() {
   sync.release(1);
}
//AQS.release
public final boolean release(int arg) {
	//释放锁
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

Sync.tryRelease

//Sync.tryRelease
//从释放资源可以看出lock是可重入锁,使用状态控制重入次数
//也就是咱们使用的时候如果有lock()方法,就一定要释放
protected final boolean tryRelease(int releases) {
	//获取状态
    int c = getState() - releases;
    //如果当前持有资源的线程不是当前线程抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果状态==0 代表当前线程无重入了
    if (c == 0) {
        free = true;
        //设置当前AQS为被线程占用
        setExclusiveOwnerThread(null);
    }
    //重置状态
    setState(c);
    return free;
}

AQS与CAS就讲到这里了,如果有帮到您帮博主点点关注啦~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值