锁机制提供了独占的方法来访问变量,并且对变量的任何修改都会对随后获得这个锁的其他线程可见.但是如果一个线程在休眠或者自旋的时候持有一个锁,那么其他线程便无法执行下去.而非阻塞算法不会受到单个线程失败的影响.
对于细粒度的操作,非阻塞算法更高效.它需要借助冲突检查机制来判断更新过程中是否存在来自其他线程的干扰,如果存在则操作失败,并且重试.现代处理器都提供了这种读-改-写(Compare-And-Swap)的指令,来实现这种复杂的并发对象.
有了硬件级别的支持,Java 5.0之后公开了一些CAS操作,原子变量类.并在其基础上实现了另外的一些并发容器.
原子变量
基础类型: AtomicBoolean,AtomicInteger,AtomicLong,LongAdder等
数组类型: AtomicIntegerArray,AtomicLongArray
引用类型: AtomicReference,AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
域更新器: AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
基础类型和数组类型
对基础的数值类型进行包装, 主要适用于计数的场景,很多流量统计的相关框架使用了这种类型的类.
引用类型
顾名思义,引用类型用于并发环境中更新整个引用.举个并发容器的例子,
如上述代码ConcurrentLinkedQueue是一个并发队列. Node为节点数据 . put 方法插入一个数据在队尾,需要移动两个指针. 一个是尾部节点指针,一个是尾部节点的下一个节点的指针也就是当前tail的next. 指针的推进都是使用原子类型来保证.
域更新器
属性修改器对比于引用类型是粒度比较小的,它通过反射实现对某个对象的属性修改,并且保证原子性.同样是ConcurrentLinkedQueue的例子,在更新Node节点的next指针的时候,不一定需要使用引用类型来包装,而是可以使用域更新器.
ABA问题
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差内会导致数据的变化.假设操作是 V.compareAndSet(A,X) ,在观测V的值之前,V 的变化是 A->B->A . 我们应该认为V的值也是发生变化的.而单纯的compareAndSet操作时感知不到这种情况的.
这类问题的解决方案是不止更新一个值.AtomicMarkableReference和AtomicStampedReference正是用来解决此类问题的.它们增加了一个标志位或者版本号的概念. 这个跟一般数据库设计中的乐观锁是异曲同工.
领取专属 10元无门槛券
私享最新 技术干货