java 无锁编程_使用CAS、FAA实现无锁编程

java 无锁编程_使用CAS、FAA实现无锁编程锁会导致性能降低,在特定情况可用硬件同步原语替代锁,保证和锁一样数据安全,同时提供更好性能。硬件同步原语(AtomicHardwarePrimitives)由计算机硬件提供的一组原子操作,较常用的原语主要是CAS和FAA两种。CAS(CompareandSwap)比较交换FAA原语(FetchandAdd)语义是,先获取变量p当前的值value,然后给变量p增加inc,最后返回变量p之…

大家好,又见面了,我是你们的朋友全栈君。

锁会导致性能降低,在特定情况可用硬件同步原语替代锁,保证和锁一样数据安全,同时提供更好性能。

硬件同步原语(Atomic Hardware Primitives)

由计算机硬件提供的一组原子操作,较常用的原语主要是CAS和FAA两种。

CAS(Compare and Swap)比较交换

FAA原语(Fetch and Add)语义是,先获取变量p当前的值value,然后给变量p增加inc,最后返回变量p之前的值value。

原语有什么特殊的呢?

用编程语言来实现,肯定是无法保证原子性的。而原语是由计算机CPU提供实现,可保证操作的原子性。

原子操作具有不可分割性,不存在并发问题。所以在某些情况下,原语可以用来替代锁,实现一些即安全又高效的并发操作。

CAS和FAA在各种编程语言中,都有相应的实现,可直接使用,各种语言底层实现一样的。

注意并不是通过系统调用实现的,系统调用的开销不小,cas本来就是为了提升性能,不会走系统调用。事实上是在用户态直接使用汇编指令就可以实现。

账户服务示例

有个共享变量balance,保存当前账户余额,然后模拟多线程并发转账,看如何使用CAS原语来保证数据的安全性。

锁实现:

package main

import (

“fmt”

“sync”

)

func main() {

// 账户初始值为0元

var balance int32

balance = int32(0)

done := make(chan bool)

// 执行10000次转账,每次转入1元

count := 10000

var lock sync.Mutex

for i := 0; i < count; i++ {

// 这里模拟异步并发转账

go transfer(&balance, 1, done, &lock)

}

// 等待所有转账都完成

for i := 0; i < count; i++ {

}

// 打印账户余额

fmt.Printf(“balance = %d \n”, balance)

}

// 转账服务

func transfer(balance *int32, amount int, done chan bool, lock *sync.Mutex) {

lock.Lock()

*balance = *balance + int32(amount)

lock.Unlock()

done

}

然后启动多协程并发执行10000次转账,每次往账户中转入1元,全部转账执行完成后,账户中的余额应该正好10000。

反复多次执行,每次balance的结果都正好是10000,那安全性没问题。

CAS实现

func transferCas(balance *int32, amount int, done chan bool) {

for {

old := atomic.LoadInt32(balance)

new := old + int32(amount)

if atomic.CompareAndSwapInt32(balance, old, new) {

break

}

}

done

}

首先,for做个没有退出条件的循环。在这个循环内,反复调用CAS尝试给账户余额+1。

CAS前置条件:只有变量balance的值等于old,才会将balance赋为new。

在for循环中执行3条语句,在并发的环境中执行,会有两种可能:

执行到第3条CAS时,没有其他线程同时改变账户余额,那可安全变更账户余额。这时CAS返回值一定true,转账成功,即可退出循环。并且CAS语句,是个原子操作,赋值安全性也可保证。

在这过程,有其他线程改变账户余额,这时是无法保证数据安全的,不能再赋值。执行CAS时,由于无法通过比较步骤,所以不会执行赋值。本次尝试转账失败,当前线程并没有对账户余额做任何变更。由于返回值为false,不会退出循环,所以会继续重试,直到转账成功退循环。

这样每次转账操作,都可通过若干次重试,在保证安全性前提下,完成并发转账。

其实该例还有更简单性能更好方案:

FAA

func transferFaa(balance *int32, amount int, done chan bool) {

atomic.AddInt32(balance, int32(amount))

done

}

java.util.concurrent.atomic.AtomicLong#getAndAdd

FAA原语

获取变量当前值,然后把它做个加法,且保证该操作的原子性,一行代码即可。你开始好奇了,那CAS还有何意义?

该案例肯定FAA更合适,但CAS适用范围更广。

类似逻辑:先读数据,做计算,然后更新数据,无论这个计算啥样,都可用CAS保护数据安全。

但FAA逻辑局限于简单加减法。所以并非说CAS没有意义。

使用CAS反复重试赋值比较耗费CPU,因为for循环如果赋值不成,会立即进入下一次循环,没有等待的。如果线程间碰撞频繁,经常反复重试,这重试的线程会占用大量CPU时间,系统性能就会下降。

缓解这问题的一个方法是使用Yield(), 大部分编程语言都支持Yield()系统调用。

Yield()作用

告诉os,让出当前线程占用的CPU给其他线程。每次循环结束前调用下Yield(),可在一定程度上降低CPU使用率,缓解该问题。也可在每次循环结束后,Sleep()小段时间,但这样性能会严重下降。

所以,这种方法它只适于线程碰撞不太频繁,即执行CAS不需要重试这样的场景。

用锁、CAS和FAA完整实现账户服务

https://github.com/shenyachen/JKSJ/blob/master/study/src/main/java/com/jksj/study/casAndFaa/CASThread.java

https://github.com/xqq1994/algorithm/blob/master/src/main/java/com/test/concurrency/MutxLock.java

https://github.com/xqq1994/algorithm/blob/master/src/main/java/com/test/concurrency/CAS.java

参考

https://time.geekbang.org/column/article/130743

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/136694.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 自旋锁和互斥锁区别在哪_互斥锁的实现

    自旋锁和互斥锁区别在哪_互斥锁的实现POSIXthreads(简称Pthreads)是在多核平台上进行并行编程的一套常用的API。线程同步(ThreadSynchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的锁机制(lock)来对多个线程之间共享的临界区(CriticalSection)进行保护(另一种常用的同步机制是barrier)。Pthreads提供了多种锁机制:…

    2022年10月22日
  • spring cloud和dubbo的主要区别[通俗易懂]

    spring cloud和dubbo的主要区别[通俗易懂]1.springcloud有注册中心eurekaDubbo无用第三方的zookeeper2.Dubbo使用RPC通讯协议,提供序列化方式如下:Dubbo:Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。RMI:RMI协议采用JDK标准的java.rmi.*实现,采用阻…

  • 『迷你教程』机器学习的Bootstrap及Python实现[通俗易懂]

    『迷你教程』机器学习的Bootstrap及Python实现[通俗易懂]文章目录引导法引导程序的配置引导程序APIBootstrap方法是一种重采样技术,用于通过对数据集进行替换采样来估计总体统计数据。它可用于估计汇总统计数据,例如均值或标准差。它在应用机器学习中用于在对未包含在训练数据中的数据进行预测时估计机器学习模型的技能。估计机器学习模型技能的结果的一个理想特性是可以用置信区间表示估计的技能,这是其他方法(例如交叉验证)不容易获得的特征。在本文中您将发现用于估计机器学习模型对未知数据的技能的引导重采样方法。bootstrap方法涉及对数据集进行迭代重采样

  • idea激活码【注册码】

    idea激活码【注册码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • 基于MATLAB GUI的串口通信

    基于MATLAB GUI的串口通信之前学过单片机对于串口通信比较了解最近在学习MATLAB发现它还可以控制串口于是通过MATLAB的GUI创建了一个串口通信的小软件效果如下如果没有单片机或者其他硬件的话我们可以直接用软件模拟串口本人选择了ConfigureVirtualSerialPortDriver这个软件软件网上就有下一个使用几天就行了 选…

  • Buck的振铃实验与分析

    Buck的振铃实验与分析上上期我们提到了buck电路的开关的振铃波形,本质原因是LC的阻尼振荡。文章偏理论,那BUCK到底是怎么产生尖峰振荡呢?要想把这个问题搞清楚,也很是不容易,所以文章有点长,请直接点赞转发加收藏。问题本期主要分析以下这两个问题:1、死区时间是什么?这里有个小台阶是什么情况?2、上下尖峰振荡是如何产生的?跟哪些因素有关?理想的BUCK的SW波形我们由浅入深,一步一步来,先看理想的开关SW波形—没有尖峰电压的波形。为了能更好的看buck电路各个点的电压电流情况.

    2022年10月23日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号