JUC包-原子类

为什么需要JUC包中的原子类

首先,一个简单的i++可以分为三步:

  1. 读取i的值

  2. 计算i+1

  3. 将计算出i+1赋给i

这就无法保证i++的原子性,即在i++过程中,可能会出现其他线程也读取了i的

值,但读取到的不是更改过后的i的值。

原子类原理(AtomicInteger为例)

原子类的原子性是通过volatile + CAS实现原子操作的。

volatile

AtomicInteger类中的value是有volatile关键字修饰的,这就保证了value的内存可见性,这为后续的CAS实现提供了基础。

CAS

通过查看源码可以发现,AtomicInteger类的值更新操作都是通过调用

getAndAddInt(Object var1, long var2, int var4)方法实现

  1. /**
  2. * Atomically adds the given value to the current value.
  3. *
  4. * @param delta the value to add
  5. * @return the previous value
  6. */
  7. public final int getAndAdd(int delta) {
  8. //返回的是修改前的值,类似于i++
  9. return unsafe.getAndAddInt(this, valueOffset, delta);
  10. }
  11. /**
  12. * Atomically adds the given value to the current value.
  13. *
  14. * @param delta the value to add
  15. * @return the updated value
  16. */
  17. public final int addAndGet(int delta) {
  18. //返回的是更新后的值,类似于++i
  19. return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
  20. }

当我们查看getAndAddInt方法的具体实现,可以发现在整个方法中存在一个循

环,这就是我们说的自旋锁,顾名思义,while语句里面的条件一直为true,这个

循环就会一直执行下去。

  1. public final int getAndAddInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  6. return var5;
  7. }

下面我们来分析getAndAddInt方法中的各个参数的具体含义:

Object var1:this,表示当前对象

long var2:valueOffset,表示当前对象的内存偏移量

int var4:delat,需要加上的数值

所以整个方法的运行流程可以归纳为:

  1. 读取传入对象this在主存中偏移量为offset位置的值赋值给var5

  2. 将var5的值与当前线程对象内存中偏移量为offset位置的值进行比较(compare)

  3. 如果相等,将var5+var4的值更新到对象内存中偏移量为offset位置(swap);如果不

    相等,就进入while循环自旋。

CAS的缺点

  1. 循环时间长,开销大

    在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却

    又一直更新不成功,循环往复,会给CPU带来很大的压力

  2. 只能保证一个共享变量的原子性操作

    CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块

    的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用

    Synchronized了

  3. ABA问题

    见下文

ABA问题

什么是ABA问题

简单来说就是CAS过程只在乎当前值期望值是否相等,只在乎最终结果,不考虑中

间变化,具体可以看下面一个简单的例子。

  1. public class Test {
  2. static AtomicInteger atomicInteger = new AtomicInteger(0);
  3. public static void main(String[] args) {
  4. atomicInteger.compareAndSet(0,1);
  5. System.out.println("线程A第一次修改:0->" + atomicInteger.get());
  6. new Thread(() -> {
  7. atomicInteger.compareAndSet(1,0);
  8. System.out.println("线程A第二次修改:1->" + atomicInteger.get());
  9. }, "testA").start();
  10. new Thread(() -> {
  11. try {
  12. //确保A线程修改完毕
  13. TimeUnit.SECONDS.sleep(1);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. atomicInteger.compareAndSet(0,2);
  18. System.out.println("线程B第一次修改:0->" + atomicInteger.get());
  19. }, "testB").start();
  20. }
  21. }

程序运行后输出的结果,由此可见AtomicInteger的CAS中间步骤有变化,但是没有被感知到。

ABA问题的解决办法

一个简单的想法是,在数据上加上时间戳(版本号),使得线程每次对变量进行修改时,不仅要对比值,还要

对比时间戳(版本号),每次修改操作都会导致时间戳(版本号)改变为新的

值;

我们通过AtomicStampedReference类引入版本号,如下图所示

  1. public class Test {
  2. //初始化数值为0,版本号为1
  3. static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(0, 1);
  4. public static void main(String[] args) {
  5. new Thread(() -> {
  6. /* compareAndSet四个参数分别为
  7. * 期望值/新的值/期望版本号/新的版本号
  8. */
  9. atomicStampedReference.compareAndSet(0, 1, 1, 2);
  10. System.out.println("数值第一次修改为" + atomicStampedReference.getReference() +
  11. " 版本号第一次修改为" + atomicStampedReference.getStamp());
  12. atomicStampedReference.compareAndSet(1, 0, 2, 3);
  13. System.out.println("数值第二次修改为" + atomicStampedReference.getReference() +
  14. " 版本号第二次修改为" + atomicStampedReference.getStamp());
  15. }, "testA").start();
  16. new Thread(() -> {
  17. try {
  18. //确保A线程修改完毕
  19. TimeUnit.SECONDS.sleep(1);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. atomicStampedReference.compareAndSet(0, 1, 1, 2);
  24. System.out.println("数值第三次修改为" + atomicStampedReference.getReference() +
  25. " 版本号第三次修改为" + atomicStampedReference.getStamp());
  26. }, "testB").start();
  27. }
  28. }

上述代码的程序运行结果如下图所示,可以看到当第三次修改的时候,虽然期望值0匹配,但是期望版本号不匹配,导致第三次修改无效。

JUC包-原子类(AtomicInteger为例)的更多相关文章

  1. juc原子类之二:基本类型原子类AtomicInteger(AtomicLong、AtomicBoolean)

    一.AtomicInteger简介 AtomicInteger, AtomicLong和AtomicBoolean这3个基本类型的原子类的原理和用法相似.以AtomicInteger对基本类型的原子类 ...

  2. JUC之原子类

    在分析原子类之前,先来了解CAS操作 CAS CAS,compare and swap的缩写,中文翻译成比较并交换. CAS 操作包含三个操作数 —— 内存位置(V).预期原值(A)和新值(B).如果 ...

  3. 对Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  4. Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  5. java的原子类 AtomicInteger 实现原理是什么?

    采用硬件提供原子操作指令实现的,即CAS.每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全. CAS机制 CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换. ...

  6. CAS你知道吗?原子类AtomicInteger的ABA问题谈谈?

    (1)CAS是什么?  比较并交换 举例1,  CAS产生场景代码? import java.util.concurrent.atomic.AtomicInteger; public class CA ...

  7. 浅析CAS与AtomicInteger原子类

    一:CAS简介 CAS:Compare And Swap(字面意思是比较与交换),JUC包中大量使用到了CAS,比如我们的atomic包下的原子类就是基于CAS来实现.区别于悲观锁synchroniz ...

  8. 从原子类和Unsafe来理解Java内存模型,AtomicInteger的incrementAndGet方法源码介绍,valueOffset偏移量的理解

    众所周知,i++分为三步: 1. 读取i的值 2. 计算i+1 3. 将计算出i+1赋给i 可以使用锁来保持操作的原子性和变量可见性,用volatile保持值的可见性和操作顺序性: 从一个小例子引发的 ...

  9. JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法

    JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...

随机推荐

  1. 第15.43节、PyQt输入部件:QAbstractSpinBox派生类QSpinBox、 QDoubleSpinBox、QDateTimeEdit、QDateEdit和QTimeEdit功能简介

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.概述 Designer输入部件中的Spin B ...

  2. 《Machine Learning in Action》—— Taoye给你讲讲Logistic回归是咋回事

    在手撕机器学习系列文章的上一篇,我们详细讲解了线性回归的问题,并且最后通过梯度下降算法拟合了一条直线,从而使得这条直线尽可能的切合数据样本集,已到达模型损失值最小的目的. 在本篇文章中,我们主要是手撕 ...

  3. 深入理解python

    1 python自身的威力 1.1 使用type.str.dir.其他内置函数 //type函数:返回任意对象的数据类型.比如:整型.字符串.列表.字典.元组.函数.类.模块,甚至类型对象都可以作为参 ...

  4. 百度前端技术学院-基础-day7.8

    任务:参考如下设计稿实现HTML页面及CSS样式 代码 点击预览 HTML 1 <!DOCTYPE html> 2 <html lang="en"> 3 & ...

  5. 【题解】P1852 跳跳棋

    link 题意 跳跳棋是在一条数轴上进行的.棋子只能摆在整点上.每个点不能摆超过一个棋子.棋盘上有3颗棋子,分别在 \(a,b,c\) 这三个位置.我们要通过最少的跳动把他们的位置移动成 \(x,y, ...

  6. CRT, lucas及其扩展形式

    CRT, lucas及其扩展形式 exgcd int exgcd(int a, int b, int &x, int &y) { if (b == 0) return a, x = 1 ...

  7. 【题解】Generator(UVA1358)

    感觉我字符串和期望都不好-- 题目链接 题意 有 \(n\) 种字符,给定一个模式串 \(S\) ,一开始字符串为空,现在每次随机生成一个 1~n 的字符添加到字符串末尾,直到出现 \(S\) 停止, ...

  8. 题解 P5401 [CTS2019]珍珠

    蒟蒻语 这题太玄学了,蒟蒻写篇题解来让之后复习 = = 蒟蒻解 假设第 \(i\) 个颜色有 \(cnt_i\) 个珍珠. \(\sum\limits_{i=1}^{n} \left\lfloor\f ...

  9. Java并发编程的艺术(六)——中断、安全停止线程

    什么是中断 Java的一种机制,用于一个线程去暂停另一个线程的运行.就是一个正在运行的线程被其他线程给打断,停止运行挂起了. 我觉得,在Java中,这种中断机制只是一种方便程序员编写进程间的通信罢了. ...

  10. WebService-问题

    1.引用问题 在用C#对接webservice的时候,常用的方法是下载vs中引用webservice的地址.然后,new对应的client就可以使用了.但在,实际应用中往往会遇到webservice访 ...