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


    什么是原子变量

    • 原子变量与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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    重写_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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    什么是CompareAndSet(CAS)?

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

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

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

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

    无锁编程?

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

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

  • 相关阅读:
    vue之封装预约类组件
    新年学新语言Go之四
    [Vue2]实现点击下拉筛选table组件
    麒麟Arm64nacos打包docker镜像说明
    【Python】读取文件的名字和文件后缀名
    JMeter集结点的使用场景以及如何使用?
    leetcode 27. 移除元素-java版本
    drf_day04
    PHP 可用的函数
    网络是如何进行通信
  • 原文地址:https://blog.csdn.net/qq_33982605/article/details/126688811