AQS模型
AbstractQueuedSynchronizer抽象队列同步器:AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)
抽象队列同步器中包括两个部分:临时资源和一个FIFO的CLH阻塞队列.
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
内部使用AQS的例子:以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
CAS模型
Java5中引入了AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如compareAndSet、incrementAndSet和getAndIncrement等方法都使用了CAS操作。都是由硬件指令来保证的原子方法。
CAS即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置V、预期原值A和新值B。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。
比较和交换(Compare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。这是作为单个原子操作完成的。
原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成
CAS 的特性:
- 通过调用JNI的代码实现
- 非阻塞算法
- 非独占锁
CAS 存在的问题:
- ABA
- 循环时间长开销大
- 只能保证一个共享变量的原子操作
CAS模型的问题
1、ABA问题
解决方法:JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。
2、CAS应用场景隐含竞争是短暂的,否则不断的自旋尝试会过度消耗CPU
解决方法加入超时设置
3、CAS只能保证一个共享变量的原子操作,解决方法是使用锁或者合并多个变量
AtomicReference提供了以无锁方式访问共享资源的能力
总结:
AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference这些原子类型,它们无一例外
都采用了基于volatile 关键字 +CAS 算法无锁的操作方式来确保共享数据在多线程操作下的线程安
全性。
- - volatile关键字保证了线程间的可见性,当某线程操作了被volatile关键字修饰的变量,其他线程可以立即看到该共享变量的变化。
- - CAS算法,即对比交换算法,是由UNSAFE提供的,实质上是通过操作CPU指令来得到保证的。CAS算法提供了一种快速失败的方式,当某线程修改已经被改变的数据时会快速失败。
- - 当CAS算法对共享数据操作失败时,因为有自旋算法的加持,对共享数据的更新终究会得到计算。总之,原子类型用自旋+CAS的无锁操作保证了共享变量的线程安全性和原子性。