Java 并发编程实战
王宝令
资深架构师
72485 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

21 | 原子类:无锁工具类的典范

AtomicXXXFieldUpdater
AtomicMarkableReference
AtomicStampedReference
方法
CAS方案中的问题
自旋
CAS操作流程
原子性
Compare And Swap
使用原子类的注意事项
死锁问题
性能
原子化的累加器
原子化对象属性更新器
原子化数组
原子化的对象引用类型
原子化的基本数据类型
CAS方法
unsafe.getAndAddLong()方法
Java 1.8版本中的实现
ABA问题
CAS+自旋
CAS指令的模拟代码
CAS指令
线程切换
阻塞状态
性能
SafeWM类的setUpper()方法
建议
无锁方案的优点
原子类概览
Java如何实现原子化的count += 1
无锁方案的实现原理
无锁方案 vs 互斥锁方案
课后思考
总结
无锁工具类的典范
原子类

该思维导图由 AI 生成,仅供参考

前面我们多次提到一个累加器的例子,示例代码如下。在这个例子中,add10K() 这个方法不是线程安全的,问题就出在变量 count 的可见性和 count+=1 的原子性上。可见性问题可以用 volatile 来解决,而原子性问题我们前面一直都是采用的互斥锁方案。
public class Test {
long count = 0;
void add10K() {
int idx = 0;
while(idx++ < 10000) {
count += 1;
}
}
}
其实对于简单的原子性问题,还有一种无锁方案Java SDK 并发包将这种无锁方案封装提炼之后,实现了一系列的原子类。不过,在深入介绍原子类的实现之前,我们先看看如何利用原子类解决累加器问题,这样你会对原子类有个初步的认识。
在下面的代码中,我们将原来的 long 型变量 count 替换为了原子类 AtomicLong,原来的 count +=1 替换成了 count.getAndIncrement(),仅需要这两处简单的改动就能使 add10K() 方法变成线程安全的,原子类的使用还是挺简单的。
public class Test {
AtomicLong count =
new AtomicLong(0);
void add10K() {
int idx = 0;
while(idx++ < 10000) {
count.getAndIncrement();
}
}
}
无锁方案相对互斥锁方案,最大的好处就是性能。互斥锁方案为了保证互斥性,需要执行加锁、解锁操作,而加锁、解锁操作本身就消耗性能;同时拿不到锁的线程还会进入阻塞状态,进而触发线程切换,线程切换对性能的消耗也很大。 相比之下,无锁方案则完全没有加锁、解锁的性能消耗,同时还能保证互斥性,既解决了问题,又没有带来新的问题,可谓绝佳方案。那它是如何做到的呢?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

原子类是并发编程中的重要工具,通过CAS指令实现原子性操作,提高了并发性能。相比互斥锁方案,无锁方案避免了加锁、解锁操作的性能消耗,同时保证了互斥性。Java SDK提供了丰富的原子类,包括原子化的基本数据类型、对象引用类型、数组、对象属性更新器和累加器。这些原子类提供了一系列方法,如原子化的加减操作、CAS操作等。无锁方案相对于互斥锁方案,优点非常多,但需要注意ABA问题。文章还介绍了原子化的对象引用类型、原子化数组、原子化对象属性更新器和原子化的累加器的相关实现。总体而言,原子类在并发编程中发挥着重要作用,但使用时需要慎之又慎。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(75)

  • 最新
  • 精选
  • 张天屹
    如果线程1 运行到WMRange or = rf.get();停止,切换到线程2 更新了值,切换回到线程1,进入循环将永远比较失败死循环,解决方案是将读取的那一句放入循环里,CAS每次自旋必须要重新检查新的值才有意义

    作者回复: 👍

    2019-04-16
    2
    111
  • Geek_ebda96
    老师你举的这个例子,自己实现CAS是不是有点不对 class SimulatedCAS{ volatile int count; // 实现 count+=1 addOne(){ do { newValue = count+1; //① }while(count != cas(count,newValue) //② } // 模拟实现 CAS,仅用来帮助理解 synchronized int cas( int expect, int newValue){ // 读目前 count 的值 int curValue = count; // 比较目前 count 值是否 == 期望值 if(curValue == expect){ // 如果是,则更新 count 的值 count= newValue; } // 返回写入前的值 return curValue; } } 2 这里是不是应该用oldValue来比较,在do里面的时候先把count的值用oldValue保存下来,传入的参数expected为oldValue,newValue为oldValue+1 do{ oldValue = count; newValue = oldValue + 1; }while(oldValue != cas(oldValue, newValue)) 望指正

    作者回复: 你的这个写法是对的👍

    2019-04-18
    13
    42
  • 长脖子树
    原子类在 java 层面虽然看起来是无锁的, 但是深入到操作系统和cpu层面仍然有锁 比如像在多处理器计算机里常常会有一种 TSL 指令, (测试并加锁 test and set lock), 它是一种硬件支持的互斥方案, 执行这个指令的cpu将锁住内存总线, 以禁止其他CPU 在本指令结束之前访问内存 这个指令类似于 intel 处理器上的 lock cmpxchg , 但在近几代的实现上, 对内存总线的锁定做了类似于分段锁的优化, 仅仅锁定部分的缓存行 参考: 1. 现代操作系统 2. https://en.wikipedia.org/wiki/Compare-and-swap 3. https://stackoverflow.com/questions/11065675/lock-prefix-of-intel-instruction-what-is-the-point 4. https://cloud.tencent.com/developer/article/1189884

    作者回复: 👍

    2020-04-25
    4
    26
  • 木卫六
    首先,or=rf.get()需要放到do{},每次需要重新获取,以防其他线程更新过导致死循环; 然后,nr是new的,我觉得应该不会发生ABA的问题(reference的compareAndSet比较的是内存地址)。另外ABA问题应该容易发生在值类型上吧,引用类型的应该几乎不会发生?对于引用类型,几乎不会发生经过至少两次new对象,最后对象放在了同一块or之前使用的内存区块上吧?

    作者回复: 我也觉得没有ABA问题

    2019-04-16
    2
    12
  • andy
    public class SafeWM { class WMRange{ final int upper; final int lower; WMRange(int upper,int lower){ // 省略构造函数实现 } } final AtomicReference<WMRange> rf = new AtomicReference<>( new WMRange(0,0) ); // 设置库存上限 void setUpper(int v){ WMRange nr; WMRange or; do{ or = rf.get(); // 检查参数合法性 if(v < or.lower){ throw new IllegalArgumentException(); } nr = new WMRange(v, or.lower); }while(!rf.compareAndSet(or, nr)); } } 这样子对吗?

    作者回复: 对👍

    2019-04-16
    4
    12
  • 郑晨Cc
    or是原始的 nr是new出来的 指向不同的内存地址 compareandset的结果永远返回false 结果是死循环?是不是应该用atomicfieldreference?

    作者回复: 👍,不过我觉得没必要用atomicfieldreference

    2019-04-16
    3
    11
  • 随风而逝
    老师,这些原子操作类在分布式程序中还有效吗?

    作者回复: 无效

    2019-04-30
    3
    7
  • 刘志兵
    老师,compareAndSwapLong方法是一个native方法,比较共享变量和expect值是否相等,相等才设置新的值x, 不明白这里的对比是怎么保证原子性的,对比也是要再读一次共享变量,然后对比吧,如果先读出来之后对比的时候被其他线程修改了,那还是会有问题

    作者回复: 最终依赖的是cpu提供的原子指令,不用我们操心。

    2019-04-17
    7
  • 刘育飞
    不明白 synchronized int cas() 这不是已经用了 同步synchronized 关键字 吗怎么会 无锁 无堵塞呢

    作者回复: cas大部分都是CPU提供的指令,这里只是模拟它的原理

    2019-10-17
    6
  • 忍者无敌1995
    例子中的模拟CAS,cas函数是加了锁的,保证整个操作的原子性;我的理解是这个只是一个模拟,实际中肯定不会加上锁的

    作者回复: 👍

    2019-05-02
    6
收起评论
显示
设置
留言
75
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部