【构建并发程序】3-原子变量

本文探讨原子变量在并发编程中的应用,如AtomicLong的使用,以及如何通过原子操作实现无锁的getUniqueId方法。着重讲解了CompareAndSet(CAS)原理及其在无锁算法提升吞吐量的作用。
摘要由CSDN通过智能技术生成

什么是原子变量

  • 原子变量与Volatile变量很相似,但使用它们的代价比使用Volatile变量的代价更高;它们用于在不使用synchronized语句的情况下,执行复杂的并发操作。
  • 原子变量有布尔型,整型,长整型,和引用类型
    AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference

什么是原子操作

  • 原子变量被存储在支持复杂的可线性化操作的内存区域中。可线性化操作是指可立刻执行的任何操作,例如Volatile变量的写入操作就是一种可线性化操作。一个复杂的线性化操作至少包含2个步骤:读取和写入; 因此我们使用术语“原子操作”,代表这种复杂的可线性化操作。

使用原子操作执行getUniqueId方法(即UID的唯一)

object two_原子操作_getUniqueId extends App {
  private val uid = new AtomicLong(0L) //定义了一个Long类型的原子变量
  def getUniqueId: Long = uid.incrementAndGet() //使用了该变量的原子操作:incrementAndGet;它会读取uid然后计算+1的值,之后将结果写会变量uid中,并返回这个结果。
  execute(println(s"Uid asynchronously : $getUniqueId")) // 创建个全局对象线程池
  println(s"Got a unique id : $getUniqueId") //main线程也执行一次getUniqueId方法
  Thread.sleep(1000)
}

重写_incrementAndGet并实现getUid

 compareAndSet使用了源码中的compareAndSwapLong(valueOffset,oldValue,newValue) valueOffset表示的是原子变量uid的值在内存中如果发生了变化,发生变化后新值所在的地址,如果oldValue的值 == valueoffSet的值,那么就将newValue赋值给ValueOffset然后地址发生偏移,此时uid的值就是newValue,返回的是一个true如果oldValue != valueOffset那么返回的就是一个false

object 重写_incrementAndGet extends App {
  private val uid: AtomicLong = new AtomicLong(0L) //定义了一个Long类型的原子变量

  @tailrec def 重写_incrementAndGet: Long = {
    val oldUid = uid.get()
    val newUid = oldUid + 1
    if (uid.compareAndSet(oldUid, newUid)) newUid else 重写_incrementAndGet
  }

  for (i <- 1 to 12) execute(println(s"ThreadName = ${Thread.currentThread().getName};Uid asynchronously : $重写_incrementAndGet")) // 创建个全局对象线程池
  println(s"Got a unique id : $重写_incrementAndGet") //main线程也执行一次getUniqueId方法
  Thread.sleep(1000)
}

什么是CompareAndSet(CAS)?

  • 原子变量还定义了其他复杂的原子操作,例如getAndSet,decrementAndGet,addAndGet之类的方法。
  • 而这些原子操作都实现了基本的原子操作(compareAndSet比较并转换操作),CAS会接收原子变量的当前值和新值,并在当前值等于预期值的情况下以原子方式将当前值替换为新值;

为什么无锁算法能够提高吞吐量?

因为执行无锁操作的线程不会尝试获取任何锁;

  • 使用原子变量实现的操作基本都是无锁的,但这并不意味着使用原子变量的程序就一定是无锁的。
  • 无锁操作的定义是:当一组线程执行一个操作时,如果不论这些线程以怎样的速度运行,它们之中至少有2个线程总是会在限定的时间内完成该操作,那么该操作就是一个无锁操作。(例如前面的重写_incrementAndGet操作方法,线程池中的线程至少有2个都会完成操作的,因为如果失败了还会调用该操作方法,而不会一直while,进入忙等待)

无锁编程?

  • 在程序中使用锁,可能会引发死锁的情况;
  • 但我们可以通过使用原子变量来实现无锁操作,如前面的”重写_incrementAndGet”就是一个通过原子变量来实现的getUid的无锁操作;

(本文章虽然采用的代码为scala代码,但java代码与Scala代码可以互相转换,且本质上两者所阐述的东西都是一致的)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值