浅谈AbstractQueuedSynchronizer原理

摘要

AbstractQueuedSynchronizer是一个线程同步的管理器,以下简称AQS。在juc包下面,大多数同步工具类都是基于AQS来实现多线程之间的同步。但它是如何做到让线程获取/释放锁,来达到获得资源的使用权的呐?简单的说,就是在AQS中有一个状态值(private volatile int state;),当某一线程需要获取/释放锁的时候,就去查看/更改这个状态值。跟具这个值来判定是否能获得资源,否则就进入由AQS管理的CLH队列中(由AbstractQueuedSynchronizer的内部类Node来实现)。在以下我总说的[获取锁]就是去检查这个状态值,请自行脑补。

阅读完本文,你能了解到什么

  • 公平锁和非公平锁的实现区别
  • CAS的原理
  • 如何获取和释放锁

内容

为了搞懂AQS的原理,我这里用一个比较常用的AQS的子类来进一步说明,ReentrantLock(重入锁)。ReentrantLock中持有引用Sync,Sync就是对AQS的继承,大家看下源代码就一目了然了。在使用ReentrantLock时,我们可以有选择性的创建公平锁和非公平锁。跟具创建实例时,传递的boolean型参数,如下构造方法;

/**
 * 创建非公平锁
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * 创建公平锁
 /
public ReentrantLock(boolean fair) {
    sync = (fair)? new FairSync() : new NonfairSync();
}

我们先分析公平锁,然后非公平锁就比较简单了

  • [公平锁]
    顾名思义,多个线程公平的获取到锁。举个例子,直白点说。比如说有5个线程想要获取锁,但是锁只能被1个线程获得,那么剩下的4个线程就被放置到CLH队列中,也就是在Node节点的链表中;当这个线程释放锁的时候,会让这个队列中,位于头节点的这个线程(如果这个头结点的线程已经被取消了那么就取下一个)获取到锁,以此类推,从而达到公平的获取到锁。

1 获取锁,调用lock方法,它会去调用父类AQS中的acquire方法。

final void lock() {
        acquire(1); // 传递的参数1是什么意思呐?请接着往下看
}

1.1 我们接着来看下acquire方法

    public final void acquire(int arg) {
        /** 这里的if判断会执行两个过程
         * 1.尝试获取锁;
         * 2.如果没获取到,就会执行添加该线程到队列中,注意默认添加的是排他锁也就是独占锁(Node.EXCLUSIVE)
         */
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
            selfInterrupt();// 如果没获取到,则自我中断(Thread.currentThread().interrupt();)
        }
    }

1.1.1 接着看tryAcquire方法,子类中分别对应公平和非公平锁获取(这里先看公平锁获取到实现)
这里写图片描述

    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前状态
        int c = getState();
        /**      
         * 如果状态是0代表可以获得锁。
         * 前面讲到,为什么acquires会传递1,其实就是,如果某一线程想要获取锁并且获取到了,它就会把这个状态改成1;
         * 那么其他线程在查看或者想要获取锁的时候,就会根据这个值来判断能否获取到锁了。
         *(讲到这里是不是对这个状态一目了然了,感觉还是没那么难吧)
         */
        if (c == 0) {
            // 这里就体现了真正的公平性,判断当前线程是不是CLH队列中第一个节点
            if (isFirst(current) && compareAndSetState(0, acquires)) {
                // 设置当前线程为独占锁的拥有者
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果状态不是0,那么说明有线程获得锁了,这时候,就判断获取该锁定线程是否是当前线程
        else if (current == getExclusiveOwnerThread()) {
            // 如果是,那么讲状态在加1,这里就是体现了重入性
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

【非公平锁】

看完公平锁,在来看非公平简直就是轻松加愉快了。这里,我们再来回顾一下公平锁,就是让CLH队列中,先入队的节点(线程)获取到锁。非公平锁就是不从队列中去取节点,而是任意线程,只要查看到state没有被占用,就可以了。看如下代码:

    // 非公平锁的锁方法
    final void lock() {
        // 首先还是通过cas比较这个状态值,查看是否可以修改。如果可以就直接设置当先线程为独占锁的拥有者
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); // 尝试获取锁,接着看如下方法
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    /**
     * 从这个方法就可以明显看出来公平锁和非公平锁尝试获取锁的区别
    */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // 这个if的判断和公平锁一样,也是拿state值来判断是否可以获取锁
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

【CAS】

(我这里想聊一聊操作系统的CAS,其实java中CAS通过调用JNI来实现,在JNI本地方法中通过C语言调用CPU指令来完成的)CAS就是compare and swap的缩写,比较并交换的意思。在AQS中,它是调用了CPU的系统指令,这是一条原语操作。可是它明明是两个操作(比较和交换),但为什么是原子操作呐?

根据查看openJDK源代码(这里我就不贴出来了,有兴趣的朋友可以去下载一份)发现,程序会判断当前处理器的类型来决定。在以前老的操作系统,比如说奔腾之前,通过是否加一个lock前缀指令锁住消息总线,使得其他处理器无法通过总线访问内存,但是这样的操作会带来很多额外的开销。如果在多处理器(目前大多数咱们用的都是多处理器)上执行,就需要加lock前缀指令;否则如果是单处理器,就不需要。因为单处理器能维护自身顺序执行的一致性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值