AQS全称是AbstractQueuedSynchronizer,即抽象的队列式的同步器,定义了一套多线程共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch.
AQS的框架如下:
fifo即first in first out,先进先出队列
CLH锁其实就是一种是基于逻辑队列非线程饥饿的一种自旋公平锁
CLH锁原理如下:
-
首先有一个尾节点指针,通过这个尾结点指针来构建等待线程的逻辑队列,因此能确保线程线程先到先服务的公平性,因此尾指针可以说是构建逻辑队列的桥梁;此外这个尾节点指针是原子引用类型,避免了多线程并发操作的线程安全性问题;
-
通过等待锁的每个线程在自己的某个变量上自旋等待,这个变量将由前一个线程写入。由于某个线程获取锁操作时总是通过尾节点指针获取到前一线程写入的变量,而尾节点指针又是原子引用类型,因此确保了这个变量获取出来总是线程安全的。
为什么使用CLH队列:因为CLH队列更容易实现取消与超时机制
具体原理可以看博客:https://javazhiyin.blog.csdn.net/article/details/108332477
关于AQS的文章:https://www.cnblogs.com/waterystone/p/4920797.html
同步器一般包括两种方法,一种是acquire 另一种是release,acquire操作阻塞调用的线程,直到或除非同步状态允许其继续执行。而release操作则是通过某种方式改变同步状态,使得一或多个被acquire阻塞的线程继续执行。
j.u.c包中并没有对同步器的API做一个统一的定义。因此,有一些类定义了通用的接口(如Lock),而另外一些则定义了其专有的版本。因此在不同的 类中,acquire和release操作的名字和形式会各有不同。例 如:Lock.lock,Semaphore.acquire,CountDownLatch.await和FutureTask.get,在这个框架 里,这些方法都是acquire操作。但是,J.U.C为支持一系列常见的使用选项,在类间都有个一致约定。在有意义的情况下,每一个同步器都支持下面的 操作:
阻塞和非阻塞(例如tryLock)同步。
可选的超时设置,让调用者可以放弃等待
通过中断实现的任务取消,通常是分为两个版本,一个acquire可取消,而另一个不可以。
AQS的思维导图如下:
futureTask的思维导图
cas 和volatile的弊端:有大量的volatile和cas进行数据修改时会产生大量的嗅探消息
缓存一致性协议MESI
在多核处理器架构上,所有的处理器是共用一条总线的,都是靠此总线来和主内存进行数据交互。当主内存的数据同时存在于多个处理的高速缓存中时,某一处理器更新了此共享数据后。会通过总线触发嗅探机制来通知其他处理器将自己高速缓存内的共享数据置为无效,在下次使用时重新从主内存加载最新数据。而这种通过总线来进行通信则称之为”缓存一致性流量“。
因为总线是固定的,所有相应可以接受的通信能力也就是固定的了,如果缓存一致性流量突然激增,必然会使总线的处理能力受到影响。而恰好CAS和volatile 会导致缓存一致性流量增大。如果很多线程都共享一个变量,当共享变量进行CAS等数据变更时,就有可能产生总线风暴。
https://www.cnblogs.com/jiagoujishu/p/13744544.html
volatile可见性原理:内存屏障
当修改了增加volatile 的变量时,会马上将变量值写回到主内存中,这时会在store 前对主内存的这个变量加锁,在store 通过总线的时候触发MESI缓存一致性协议,通过总线嗅探器将其他cpu工作内存中的此变量置为无效状态(涉及内存屏障)。当次cpu 完成变量的write 操作时,在对变量进行解锁。