Java学习笔记-多线程之CAS

CAS (Comper And Swap)

CAS的原理

利用现代处理器都支持的CAS指令

循环这个指令,直到成功为止

image-20200707110755067

CAS的问题

  • ABA问题

    有两个线程对同一个变量操作,线程1:如果变量为A,那么将变量改为B,线程2:将变量从A改为C,再改为A.假设线程2执行的速度更快,将变量修改了一边,但是线程1做compare运算时,并没有发现变量发生改变,将变量改为B

  • 开销问题

    如果CAS运算长期不成功,处在自旋状态,将会增加CPU的开销

  • 只能保证一个共享变量的原子操作

JDK中相关的原子操作类

  • 更新基本类型类:AtomicBooleanAtomicIntegerAtomicLong

    • int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果.
    • boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值.
    • int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值.
    • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值.
  • 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

    • int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加.
    • boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值.
    • 需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组.
  • 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference

    原子更新基本类型的AtomicInxteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类.Atomic包提供了以下3个类.

    AtomicReference

    原子更新引用类型.

    AtomicStampedReference

    利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA问题了.这就是AtomicStampedReference的解决方案.AtomicMarkableReferenceAtomicStampedReference差不多,AtomicStampedReference是使用pair的int stamp作为计数器使用,AtomicMarkableReference的pair使用的是boolean mark.AtomicStampedReference可能关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过,方法都比较简单.

    AtomicMarkableReference

    原子更新带有标记位的引用类型.可以原子更新一个布尔类型的标记位和引用类型.构造方法是AtomicMarkableReference(V initialRef,booleaninitialMark).

使用AtomicStampedReference解决ABA问题

val asr = AtomicStampedReference<String>("chen", 0)
fun main() {
    val oldStamp = asr.stamp
    val oldRef = asr.reference
    println("$oldRef version - $oldStamp")

    val thread1 = Thread {
        println("${Thread.currentThread().name}: " +
                "当前变量值:$oldRef version - $oldStamp " +
                "${asr.compareAndSet(oldRef, oldRef +
                        " - kotlin", oldStamp, oldStamp + 1)}")
    }
    val thread2 = Thread {
        println("${Thread.currentThread().name}: " +
                "当前变量值:${asr.reference} version - ${asr.stamp} " +
                "${asr.compareAndSet(asr.reference, asr.reference +
                        " ++ ", oldStamp, oldStamp + 1)}")
    }

    thread1.apply {
        start()
        join()
    }
    thread2.apply {
        start()
        join()
    }

    println("${asr.reference} version - ${asr.stamp}")
}

CAS与Synchronized的区别

Synchronized是基于悲观锁实现的,在有线程要访问同步代码块时,它首先得到锁,退出或抛出异常时必须释放锁,当线程进入和退出阻塞状态时,需要对上下文进行处理,会造成额外的内存和时间消耗.

CAS是乐观锁的一种实现方式.用于并发量不高的场景.

两种方法都无法相互取代.有各自的适用的领域

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值