并发变成实战-原子变量与非阻塞同步机制
创始人
2024-05-28 12:53:33
0

文章目录

  • 1.锁的劣势
  • 2.硬件对并发的支持
    • 2.1 比较并交换
    • 2.2 非阻塞的计数器
    • 3.原子变量类
    • 3.1 原子变量是一种“更好的volatile”
    • 3.2 性能比较:锁与原子变量
    • 4.非阻塞算法
    • 4.1 非阻塞的栈
    • 4.2 非阻塞的链表
    • 4.3 ABA问题

非阻塞算法设计和实现上要复杂的多,但在可伸缩性和活跃性上拥有巨大的优势。
原子变量提供了与volatile变量相同的语义,此外还支持原子的更新操作,从而更适用于实现计数器、序列发生器和统计数据收集等。

1.锁的劣势

多个线程请求锁时,一些线程将被挂起并且在稍后恢复运行。当线程恢复执行时,必须等待其他线程执行完他们的时间片后才能被调度执行。挂起和恢复线程等过程存在很大的开销,并且通常存在较长时间的中断。
volatile更轻量级的同步机制,不会发生上下文切换或线程调度等操作。局限:虽然提供了可见性,但不能用于构建原子的复合操作。因此当一个变量依赖其它变量或新值依赖于旧值时,就不能用volatile(例如,++i 包含了3个独立的操作–获取变量当前值,加1,写入新值,整个过程必须是原子的)。
锁定时,当一个线程等待锁时,它不能做其它的事情。如果一个线程在持有锁时被延迟执行(如缺页错误、调度延迟等),所有需要这个锁的其它线程都无法执行。如果持有锁的线程永久阻塞,那么程序将永远无法继续执行。

2.硬件对并发的支持

独占锁是一种悲观锁–假设最坏的情况
对于细粒度的操作,有一种乐观的方法,可以不发生干扰的情况下完成更新操作。这种方法需要借助冲突检查机制来判断在更新过程中是否存在来自其他线程的干扰,如果存在,这个操作将失败,并且可以重试(CAS等)。

2.1 比较并交换

CAS包含了3个操作数–需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当V的值等于A时,CAS才会用原子方式用新值B来更新V的值,否则不会执行任何操作。无论位置V的值是否等于A,都将返回V原有的值。
CAS含义:我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少。
CAS是一项乐观的技术。如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。

/*** 模拟CAS操作*/
public class SimulatedCAS {private int value;public synchronized int get() {return value;}public synchronized int compareAndSwap (int expectedValue,int newValue) {int oldValue = value;if (oldValue == expectedValue) {value = newValue;}return oldValue;}public synchronized boolean compareAndSet(int expectedValue,int newValue) {return (expectedValue == compareAndSwap(expectedValue,newValue));}
}

多个线程尝试更新同一个变量时,只有一个线程能更新变量的值,而其他线程都将失败。失败的线程不会被挂起,而是会再次尝试。由于线程竞争失败后不会阻塞,因此他可以决定是否重新尝试,或者执行一些恢复操作,或者不执行任何操作。

2.2 非阻塞的计数器

使用CAS实现一个简单的非阻塞计数器

public class CasCounter {private SimulatedCAS value;public int getValue() {return value.get();}public int increment() {int v;do {v = value.get();} while (v != value.compareAndSwap(v,v + 1)) {return v + 1;}}
}

通常,反复的重试是一种合理的策略,但一些竞争激烈的情况下,更好的方式是在重试之前首先等待一段时间或者回退,从而避免造成活锁问题。
CasCounter 不会阻塞,如果其他线程同时更新计数器,那么会多次执行重试操作。(如果仅需要一个计数器或序列生成器,那么可以直接使用AtomicInteger或AtomicLong,他们能提供原子的递增方法)
竞争程度不高时,基于CAS的计数器在性能上远胜于基于锁的计数器。

3.原子变量类

原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger表示一个int类型的值,并提供了get和set方法。发生竞争时有很好的可伸缩性。
共12个原子变量类,分为4组:标量类,更新器类,数组类以及复合变量类。
最常用的原子变量就是标量类:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。他们都支持CAS。
原子数组类(只支持Integer、Long、Reference版本)中的元素可以实现原子更新。原子数组类为数组的元素提供了volatile类型的访问语义,这是普通数组不具备的特性–volatile类型的数组仅在数组引用上具有volatile,在其元素上没有。
原子的标量类扩展了Number类,但并没有扩展一些基本类型的包装类,如Integer或Long,因为基本类型的包装类是不可修改的,而原子变量类是可修改的。

3.1 原子变量是一种“更好的volatile”

public class CasNumberRange {private static class IntPair {//不变形条件: lower <= upperfinal int lower;final int upper;private IntPair(int lower, int upper) {this.lower = lower;this.upper = upper;}}//使用AtomicReference和IntPair来保存状态private final AtomicReference values = new AtomicReference<>(new IntPair(0, 0));public int getLower() {return values.get().lower;}public int getUpper() {return values.get().upper;}public void setLower(int i) {while (true) {IntPair oldv = values.get();if (i > oldv.upper) {throw new IllegalArgumentException("Can not set lower to " + i + " > upper");}IntPair newv = new IntPair(i, oldv.upper);//CAS 避免竞态if (values.compareAndSet(oldv, newv)) {return;}}}
}

3.2 性能比较:锁与原子变量

低竞争情况下,原子变量的性能高于锁,高竞争情况下,锁性能高于原子变量
但实际情况中,原子变量在可伸缩性上要高于锁,因为在应对常见的竞争程度时,原子变量的效率会更高。

4.非阻塞算法

4.1 非阻塞的栈

非阻塞算法通常比基于锁的算法更复杂。算法关键在于,找出如何将原子修改的范围缩小到单个变量上,同时还要维护数据一致性。
栈时最简单的链式数据结构, 每个元素只指向一个元素

public class ConcurrentStack {AtomicReference> top = new AtomicReference<>();public void push(E item) {Node newHead = new Node<>(item);Node oldHead;do {oldHead = top.get();//新节点的next指向当前栈顶newHead.next = oldHead;//使用CAS把新节点放到栈顶//如果开始插入节点前,栈顶没有发生变化,CAS就会成功更新栈顶//如果栈顶发生变化(被其他线程修改),CAS会失败,并根据新的栈状态来更新节点} while (top.compareAndSet(oldHead,newHead));}private static class Node {public final E item;public Node next;public Node(E item) {this.item = item;}}
}

非阻塞算法特性:某工作的完成具有不确定性,必须重新执行。
CAS既能提供原子性,又能提供可见性,所以非阻塞算法是线程安全的。

4.2 非阻塞的链表

链表需要单独维护头指针和尾指针来快速访问。有两个指针指向位于尾部的节点:当前最后一个元素的next指针,以及尾节点。当插入一个元素时,这两个指针都需要采用原子操作来更新。
技巧

  • 即使在一个包含多个步骤的更新操作中,也要确保数据结构总是处于一致的状态。B线程到达时,如果A正在执行更新操作,B不能立即开始自己的更新操作。然后B可以等待(通过反复检查队列的状态)并直到A完成
  • 如果B到达时发现A正在修改数据结构,那么在数据结构中应该有足够多的信息,使得B能完成A的更新操作。如果B帮助A完成了更新操作,那么B可以执行自己的操作,而不用等A操作完成。当A恢复后视图完成其操作时,会发现B已经替他完成了。
    空队列通常包含一个“哨兵节点(Sentinel)”或者“哑节点(Dummy)”,并且头结点和尾结点在初始化时都指向该哨兵节点。尾节点通常要么指向哨兵节点(如果队列为空),即队列的最后一个元素,要么(当有操作正在执行更新操作时)指向倒数第二个元素。
public class LinkedQueue {private static class Node {final E item;final AtomicReference> next;private Node(E item, AtomicReference> next) {this.item = item;this.next = next;}}private final Node dummy = new Node<>(null,null);private final AtomicReference> head = new AtomicReference<>(dummy);private final AtomicReference> tail = new AtomicReference<>(dummy);//插入元素需要更新两个指针public boolean put(E item) {Node newNode = new Node<>(item, null);while (true) {Node curTail = tail.get();Node tailNext = curTail.next.get();if (curTail == tail.get()) {if (tailNext != null) {//队列处于中间状态,推进尾结点tail.compareAndSet(curTail,tailNext);} else {//处于稳定状态,尝试插入新节点if (curTail.next.compareAndSet(null,newNode)) {//插入操作成功,尝试推进尾结点tail.compareAndSet(curTail,newNode);return true;}}}}}
}

插入元素需要更新两个指针。首先更新当前最后一个元素的next指针,将新节点链接到队尾,然后更新尾结点,将其指向这个新的元素。
当队列处于稳定状态,尾结点的next域降为空,如果队列处于中间状态,那么tail.next将为非空。因此任何线程都能通过检查tail.next来获取队列当前的状态。

4.3 ABA问题

CAS判断值相等的间隙,值是否发生过改变
解决方法:改变引用值的时候,增加一个版本号。每次改变的操作都更新版本号。

相关内容

热门资讯

苹果系统安卓爱思助手,系统兼容... 你有没有发现,手机的世界里,苹果系统和安卓系统就像是一对欢喜冤家,总是各有各的粉丝,各有各的拥趸。而...
安卓系统占用很大内存,揭秘内存... 手机里的安卓系统是不是让你感觉内存不够用,就像你的房间堆满了杂物,总是找不到地方放新东西?别急,今天...
安卓系统p30,安卓系统下的摄... 你有没有发现,最近安卓系统P30在手机圈里可是火得一塌糊涂呢!这不,我就来给你好好扒一扒这款手机的那...
siri被安卓系统进入了,智能... 你知道吗?最近科技圈可是炸开了锅,因为一个大家伙——Siri,竟然悄悄地溜进了安卓系统!这可不是什么...
最强挂机系统和安卓区别,揭秘安... 亲爱的读者,你是否曾在游戏中遇到过这样的困扰:一边想要享受游戏带来的乐趣,一边又不想放弃手中的零食或...
安卓系统为什么设系统盘,保障稳... 你有没有想过,为什么安卓系统里会有一个叫做“系统盘”的东西呢?这可不是随便设置的,背后可是有大学问的...
王者怎么加安卓系统的,轻松提升... 你有没有想过,你的手机里那款超酷的王者荣耀,怎么才能让它更好地在你的安卓系统上运行呢?别急,今天就来...
安卓手机系统怎么开热点,共享网... 你有没有想过,当你身处一个没有Wi-Fi信号的地方,而你的安卓手机里却存满了精彩视频和游戏时,是不是...
安卓系统11的平板电脑,性能升... 你有没有发现,最近平板电脑市场又热闹起来了?没错,安卓系统11的新一代平板电脑正在悄悄地走进我们的生...
安卓手机系统创始人,安卓手机系... 你有没有想过,那些陪伴我们每天生活的安卓手机,它们的灵魂是谁赋予的呢?没错,就是那位神秘而又传奇的安...
安卓11系统速度提升,体验再升... 你知道吗?最近安卓系统又升级啦!这次可是直接跳到了安卓11,听说速度提升了不少呢!是不是很心动?那就...
安卓5.1原生系统设置apk,... 你有没有想过,你的安卓手机里那些看似普通的设置,其实隐藏着不少小秘密呢?今天,就让我带你一探究竟,揭...
手机安卓系统玩音游,畅享指尖音... 你有没有发现,现在手机上的游戏种类越来越丰富,尤其是音游,简直让人爱不释手!今天,就让我来给你详细介...
安卓系统与win10,系统融合... 你有没有想过,为什么你的手机里装的是安卓系统,而电脑上却是Windows 10呢?这两种操作系统,就...
苹果系统王者安卓系统可以登吗,... 你有没有想过,为什么苹果系统的手机那么受欢迎,而安卓系统的手机却也能在市场上占有一席之地呢?今天,咱...
安卓系统怎么重制系统还原,安卓... 手机用久了是不是感觉卡得要命,想给它来个大变身?别急,今天就来教你怎么给安卓手机重置系统,让它焕然一...
安卓9系统怎样应用分身,轻松实... 你有没有发现,手机里的APP越来越多,有时候一个APP里还要处理好多任务,分身功能简直就是救星啊!今...
获取安卓系统的ip地址,轻松获... 你有没有想过,你的安卓手机里隐藏着一个神秘的IP地址?没错,就是那个能让你在网络世界里找到自己的小秘...
LG彩电安卓系统升级,畅享智能... 你家的LG彩电是不是最近有点儿“闹别扭”,屏幕上时不时地跳出个升级提示?别急,今天就来给你详细说说这...
阴阳师安卓苹果系统,安卓与苹果... 亲爱的玩家们,你是否曾在深夜里,手握手机,沉浸在阴阳师的神秘世界?今天,就让我带你一起探索这款风靡全...