15-原子变量与非阻塞同步机制
锁的劣势:
1,线程等待锁,不能做其他事情(如果占用锁的线程阻塞,则等待队列一直等待),优先级反转
2,同步成本比较高
3,当前线程获取锁失败,则将当前线程挂起,活跃性问题,cpu利用率低
volitile比锁更轻量的同步机制
优点:没有上下文切换和线程调度,可见性保证
缺点:不能构建原子的复合操作
硬件对并发的支持
1,比较并交换指令 CAS:
三个参数(内存位置,比较的值,新值)
如果值被其他线程修改,CAS能检测到这个错误,返回失败但不会挂起(相比锁而言)
减少了活跃性风险
存在活锁问题
缺点:
需要调用者处理竞争问题:重试,回退,放弃(锁能自动处理竞争问题,挂起(阻塞)等待线程)
CAS的性能跟CPU处理器数有关,需要处理处理器之间的同步
2,JVM对对CAS的支持
编译为对应的CAS指令,如果硬件平台不支持CAS,则自旋锁(spin)
原子变量类:
比锁的粒度更细(竞争范围缩小到单个变量上),量级更轻
快速路径
泛化的volatile变量,支持原子的 有条件的 读-改-写操作
更高的可伸缩性
4组共12个原子变量类:
标量类:AtomicInteger AtomicLong AtomicBoolean AtomicReference(支持算数运算:AtomicInteger AtomicLong)
其他基本类型(char,byte)可以通过类型转换为int,浮点类型(floatToIntBits,floatToLongBits)来使用标量类
标量类没有扩展基本类型包装类,因为基本类型包装类是不可修改final的
更新器类:AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater
数组类:AtomicReferenceArray AtomicIntegerArray AtomicLongArray
数组元素可以实现原子更新,给数组元素提供了volatile类型的访问语意,volatile数组仅对数组引用本身有volatile语意
复合变量类:AtomicMarkableReference, AtomicStampedReference(AtomicReference的功能增强版,解决CAS的ABA问题)
1,better version of volatile
竞态条件
破坏数据一致性
2,性能比较: 锁 vs 原子变量
可伸缩性差异
超高度竞争条件下,原子变量调用者需要处理竞争的管理(重试),重试在激烈的竞争环境下导致了更多的竞争
但总的来说,原子变量的性能将超过锁,锁会挂起线程,降低cpu使用率和共享内存总线上的同步通信量
可伸缩性 原子变量 > 锁
中低端强度竞争 原子变量 > 锁
高强度竞争 原子变量 < 锁
ThreadLocal是无锁的
非阻塞算法
1,非阻塞的栈
关键:如何将原子修改的范围缩小到单个变量上,同时还要维护数据一致性
栈的结构:每个元素仅指向一个元素,每个元素只被一个元素引用
compareAndSet既提供了原子性,又提供了可见性
竞争失败时必须重试
2,非阻塞的链表
队列的结构:头结点和尾节点,头指针和尾指针,有两个指针会指向尾节点,最后一个元素的next指针和尾指针,插入元素时需要保证这两个指针操作的原子性操作
3,原子的域更新器
更新目标类实例中的指定域,原子性更新,原子性保证更弱一点(因为只能保证使用其他使用原子域更新器的线程的原子性)
volatile域
4,ABA问题
CAS引起的一个问题:三个线程CAS一个值A,t1:A->B,t2:B->A,t3看到A值可能没有变,认为A没有被更新,实际上是被更新过
解决方案:
增加版本号,每更新一次,增加一次版本号
支持在两个变量上执行原子的条件更新 AtomicMarkableReference, AtomicStampedReference