AQS(AbstractQueuedSynchronizer )

2020-06-24  本文已影响0人  仕明同学

AbstractQueuedSynchronizer ?

队列同步器AbstractQueuedSynchronizer(以下简称同步器或AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。并发包的大师(Doug Lea)期望它能够成为实现大部分同步需求的基础。


image.png
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock getLock = lock.readLock(); // 读锁
    private final Lock setLock = lock.writeLock(); // 写锁

ReentrantLock是一个互斥锁,也是一个可重入锁(Reentrant就是再次进入的意思)。ReentrantLock锁在同一个时间点只能被一个线程锁持有,但是它可以被单个线程多次获取,每获取一次AQS的state就加1,每释放一次state就减1

AQS使用方式和其中的设计模式

image.png

AQS模板方法模式

同步器的设计基于模板方法模式。模板方法模式的意图是,定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

模板方法模型是一种行为设计模型。模板方法是一个定义在父类别的方法,在模板方法中会呼叫多个定义在父类别的其他方法,而这些方法有可能只是抽象方法并没有实作,模板方法仅决定这些抽象方法的执行顺序,这些抽象方法的实作由子类别负责,并且子类别不允许覆写模板方法。

image.png

在安卓中 onDraw、onMeasure、onLayout 其实就是模板方法 ,还有Activity的生命周期也是

访问或修改同步状态的方法

重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态。

CLH队列锁

当一个线程需要获取锁时:

前驱结点释放锁,线程A的myPred所指向的前驱结点的locked字段变为false,线程A就可以获取到锁。
CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail)。CLH队列锁常用在SMP体系结构下。
Java中的AQS是CLH队列锁的一种变体实现。

Lock &ReentrantLock&ReentrantReadWriteLock

image.png

ReentrantLock的使用,如果是使用 synchronize 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。而 ReentrantLock 就是一个普通的类,它是基于AQS(AbstractQueuedSynchronizer)来实现的。

ReentrantLock是一个重入锁:一个线程获得了锁之后仍然可以反复的加锁,不会出现自己阻塞自己的情况。


image.png
image.png

ReentrantLock 分为公平锁和非公平锁,可以通过构造方法来指定具体类型

//默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁和公平锁不同的地方是:非公平锁,lock的时候,直接开始CAS,但是公平锁会先查询一下:查询是否有任何线程在等待获取更长的时间比当前线程。默认一般使用非公平锁,它的效率和吞吐量都比公平锁高的多
nonfairTryAcquire方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。同步状态表示锁被一个线程重复获取的次数。

image.png

nonfairTryAcquire(int acquires)方法,对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,而公平锁则不同。tryAcquire方法,该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

image.png

公平锁与非公平锁的差异主要在获取锁:
公平锁就相当于买票,后来的人需要排到队尾依次买票,不能插队。
而非公平锁则没有这些规则,是抢占模式,每来一个人不会去管队列如何,直接尝试获取锁。

公平锁与非公平锁,来之美团技术的理解,可以说非常的形象了


假设有一口水井,有管理员看守,管理员有一把锁,只有拿到锁的人才能够打水,打完水要把锁还给管理员。每个过来打水的人都要管理员的允许并拿到锁之后才能去打水,如果前面有人正在打水,那么这个想要打水的人就必须排队。管理员会查看下一个要去打水的人是不是队伍里排最前面的人,如果是的话,才会给你锁让你去打水;如果你不是排第一的人,就必须去队尾排队,这就是公平锁。


image.png

对于非公平锁,管理员对打水的人没有要求。即使等待队伍里有排队等待的人,但如果在上一个人刚打完水把锁还给管理员而且管理员还没有允许等待队伍里下一个人去打水时,刚好来了一个插队的人,这个插队的人是可以直接从管理员那里拿到锁去打水,不需要排队,原本排队等待的人只能继续等待


image.png

ReentrantLock里面有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的。它有公平锁FairSync和非公平锁NonfairSync两个子类。ReentrantLock默认使用非公平锁,也可以通过构造器来显示的指定使用公平锁。

看一下公平锁与非公平锁的加锁方法的源码:

image.png
通过上图中的源代码对比,我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors() image.png

综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。


ReentrantReadWriteLock 是读写锁,一般我们使用在读和写的场景,比如说实现一个图片的缓存,ImageCache Java中的源码实现的思路

private ReadWriteLock lock = new ReentrantReadWriteLock();


所以说你在牛逼,安卓如果想脱离Java生态,根本不现实,哈哈!但是也可以说写源码的真牛逼

上一篇下一篇

猜你喜欢

热点阅读