java synchronized atomic_Java多线程:CAS与java.util.concurrent.atomic

乐观锁与悲观锁

悲观锁

总是假设最坏的情况,每次获取数据都认为别人会修改,所以拿数据时会上锁,一直到释放锁不允许其他线程修改数据。Java中如synchronized和reentrantLock就是这种实现。

乐观锁

总是假设最好的情况,每次去拿数据时都认为别人不会修改,所以不上锁,等更新数据时判断一下在此期间是否有其他人更新过这个数据,可以使用CAS算法实现。乐观锁适用于多读少写的应用类型,可以大幅度提高吞吐量。乐观锁的实现机制主要包括版本号机制(给数据加一个版本号,数据被修改版本号会加一,更新时读取版本号,若读取到的版本号和之前一致才更新,否则驳回)和CAS算法(下详)。

CAS算法

JDK5增加java.util.concurrent包,其下类通过CAS算法实现了乐观锁实现。

CAS即compare and swap,是一种有名的无锁算法,即不使用锁的情况下实现多线程之间的变量同步,也叫非阻塞同步。

CAS有三个操作数,内存值V,旧的预期内存值A,要修改的新值B,当且仅当A=V,才将内存值V修改为B,否则不会执行任何操作。一般情况下CAS是一个自旋操作,即不断重试。

CAS只能保证一个共享变量的原子操作,当操作涉及跨多个共享变量时CAS无效。但可用AtomicReference来保证引用对象之间的原子性。

CAS开销

CAS是CPU指令集的操作,只有一步的原子操作,所以非常快,但CAS的开销主要在于cache miss问题。如图

ce7553a32468

image

这是一个8核CPU系统,共有4个管芯,每个管芯中有两个CPU,每个CPU有cache,管芯内有一个互联模块,让管芯的两个核可以互相通信。图中的系统连接模块可以让四个管芯通信。例如,此时CPU0进行一个CAS操作,而该变量所在的缓存线在CPU7的高速缓存中,则流程如下:

CPU检查本地缓存,没有找到缓存线。

请求被转发到CPU0和CPU1的互联模块,检查CPU1的本地高速缓存,没有找到缓存线。

请求被转发到系统互联模块,检查其他三个管芯,得知缓存线在CPU6和CPU7所在的管芯中。

请求被转发到CPU6和CPU7的互联模块,检查这两个CPU的高速缓存,在CPU7中找到缓存线。

CPU7将缓存线发给互联模块,并刷新自己的缓存线。

CPU6和CPU7的互联模块将缓存线发送给系统互联模块。

系统互联模块将缓存线发送给CPU0和CPU1的互联模块。

CPU0对高速缓存中的变量致性CAS操作。

此外,资源竞争时,CAS会自旋(不成功就循环到成功),

ABA问题

CAS一个常见问题是ABA问题,即变量V初次读时是A值,被赋值时也是A值,但期间变量被赋值成B值,CAS会误认为他从没被修改过。JDK1.5后的AtomicStampedReference类提供了监测ABA问题的能力,其中的compareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志等于预期标志,全部相等则以原子方式将该引用和该标志的值设置为给定的更新值。

CAS与synchronized

资源竞争少时,synchronized同步锁进行线程阻塞,唤醒切换,用户内核态间切换,浪费额外CPU资源,CAS基于硬件实现,不进入内核,不切换线程,操作自旋几率小,CAS有更高的性能。

资源竞争严重时,CAS自旋概率较大,从而浪费更多的CPU资源,效率低于synchronized。

java.util.concurrent.atomic

jdk1.5提供了一组原子类,由CAS对其实现。其中的类可以分为四组:

AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

AtomicIntegerArray,AtomicLongArray

AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

其作用为对单一数据的操作实现原子化,无需阻塞代码,但访问两个或两个以上的atomic变量或对单个atomic变量进行2次或2次以上的操作被认为是需要同步的以便这些操作是一个原子操作。

AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference

这四种类型用来处理Boolean,Integer, Long, 对象,包含以下方法:

构造函数,默认值分别为false, 0, 0, null。带参数则参数为初始化数据。

set()和get()方法,常规的设置/读取值,非原子操作。

lazySet(),设置值,原子操作。

getAndSet()相当于先使用get再set,但是是一个原子操作。

compareAndSet(expectedData, newData),接受两个参数,若atomic内数据和期望数据一致,则将新数据赋值给atomic数据返回true,否则不设置并返回false。

weakCompareAndSet(expectedData, newData),与前者类似,但更高效,不同的是可能会返回虚假的失败,不提供排序的保证,最好用于五关于happens-before的程序。

对于AtomicInteger, AtomicLong,还实现了getAndIncrement(),increateAndGet(),getAndDecreate(),decreateAndGet(),addAndGet(),getAndAdd()方法,以实现加减法的原子操作。剩下部分代码我还在阅读中,不定期补充。

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值