1、AQS ( AbstractQueuedSynchronizer )是一个用来构建锁和同步器,(所谓同步,是指线程之间的通信、协作)的框架。
Lock 包中的各种锁(如常见的 ReentrantLock, ReadWriteLock), concurrent 包中的各种同步器(如 CountDownLatch, Semaphore, CyclicBarrier)都是基于 AQS 来构建,所以理解 AQS 的实现原理至关重要。
信号量:
主要用来协调并发程序对共享资源的访问;
信号量由一个共享整型变量 S 和两个原子操作 PV 组成,S 只能通过 P 和 V 操作来改变。
P 操作:即请求资源,意味着 S 要减 1,如果 S < 0, 则表示没有资源了,此时线程要进入等待队列(同步队列)等待。
V 操作: 即释放资源,意味着 S 要加 1, 如果 S 小于等于 0,说明等待队列里有线程,此时就需要唤醒线程。
信号量机制的引入解决了进程同步和互斥问题,但信号量的大量同步操作分散在各个进程中不便于管理,还有可能导致系统死锁(P、V顺序颠倒)。
把所有进程对某一种临界资源的同步操作都集中起来,构成一个所谓的秘书进程。凡要访问该临界资源的进程,都需先报告秘书,由秘书来实现诸进程对同一临界资源的互斥使用,这种机制就是管程。
管程是一种在信号量机制上进行改进的并发编程模型,解决了信号量在临界区的 PV 操作上配对的麻烦,把配对的 PV 操作集中在一起而形成的并发编程方法理论,极大降低了使用和理解成本。
管程由四部分组成:
1管程内部的共享变量。
2管程内部的条件变量。
3管程内部并行执行的进程。
4对于局部与管程内部的共享数据设置初始值的语句。
管程就是一个对象监视器。任何线程想要访问该资源(共享变量),就要排队进入监控范围。进入之后,接受检查,不符合条件,则要继续等待,直到被通知,然后继续进入监视器。需要注意的事,信号量和管程两者是等价的,信号量可以实现管程,管程也可以实现信号量,只是两者的表现形式不同而已,管程对开发者更加友好。
管程为了解决信号量在临界区的 PV 操作上的配对的麻烦,把配对的 PV 操作集中在一起,并且加入了条件变量的概念,使得在多条件下线程间的同步实现变得更加简单。
[图片上传失败...(image-1fc84a-1696860249535)]
一来管程是解决并发问题的万能钥匙,
二来 AQS 是基于 Java 并发包中管程的一种实现,所以理解管程对理解 AQS 会大有帮助.
AQS是一个用来构建锁和同步器的框架,它维护了一个共享资源 state 和一个 FIFO 的等待队列(即上文中管程的入口等待队列),底层利用了 CAS 机制来保证操作的原子性。
state 初始化 0,在多线程条件下,线程要执行临界区的代码,
必须首先获取 state,某个线程获取成功之后, state 加1,
其他线程再获取的话由于共享资源已被占用,所以会到 FIFO 等待队列去等待,
等占有 state 的线程执行完临界区的代码释放资源( state 减 1)后,会唤醒 FIFO 中的下一个等待线程(head 中的下一个结点)去获取 state。
state 由于是多线程共享变量,所以必须定义成 volatile,以保证 state 的可见性, 同时虽然 volatile 能保证可见性,但不能保证原子性,所以 AQS 提供了对 state 的原子操作方法,保证了线程安全。
另外 AQS 中实现的 FIFO 队列(CLH 队列)其实是双向链表实现的,由 head, tail 节点表示,head 结点代表当前占用的线程,其他节点由于暂时获取不到锁所以依次排队等待锁释放。
首先要知道 ReentrantLock 是独占锁,也有公平和非公平两种锁模式;
独占锁:即其他线程只有在占有锁的线程释放后才能竞争锁,有且只有一个线程能竞争成功。
共享锁:即共享资源可以被多个线程同时占有,直到共享资源被占用完毕。
所谓公平锁即大家取号后老老实实按照先来后到的顺序在侯诊室依次等待叫号,如果是非公平锁呢,新来的病人(线程)很霸道,不取号排队 ,直接去抢先看病,占有医生(不一定成功)。
https://blog.csdn.net/striveb/article/details/86761900
网友评论