[并发编程系列]Java中的原子操作类

1. 原子操作类的作用

当程序更新一个变量时,如果多个线程同时更新该变量,可能会得到期望以外的值。比如i=1, 线程A更新i+1, 同时线程B更新I+1,经过两个线程的操作,最终变量i的值可能不是3,而是2。因为线程A、B拿到的i的值都是1,这就是线程不安全的更新操作。我们可以用synchronized来解决这样的问题,synchronized可以保证多线程之间的同步,以保证多个线程不会同时操作变量i。 但是在JDK1.5开始,就提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了更为简单高效、线程安全的方式来更新一个变量的值。

2. 原子操作类基本分类
  • 原子更新基本类型(3个)
    1. AtomicBoolean 原子更新布尔类型
    2. AtomicInteger 原子更新整型
    3. AtomicLong 原子更新长整型
  • 原子更新数组(3个)
    1. AtomicIntegerArray 原子更新整形数组中的元素
    2. AtomicLongArray 原子更新长整型数组中的元素
    3. AtomicReferenceArray 原子更新引用类型数组中的元素
  • 原子更新引用类型(3个)
    1. AtomicReference 原子更新引用类型
    2. AtomicReferenceFieldUpdater 原子更新引用类型中的字段
    3. AtomicMarkableReference 原子更新带有标记位的引用类型
  • 原子更新字段类(3个)
    1. AtomicIntegerFieldUpdater 原子更新整形字段
    2. AtomicLongFieldUpdater 原子更新长整型字段
    3. AtomicStampedReference 原子更新带有版本号的引用类型
3. CAS方式实现原子操作基本原理

JVM中CAS操作主要是利用了处理器提供的CMPXCHG执行实现。基本的思路就是利用循环进行CAS操作,直到成功为止。CAS主要涉及到三个操作数,内存中的值(V)、旧的预期值(A)、需要修改的新值(B),当且仅当V==A时,才会将V值修改为B值,否则什么都不做,并且通过一个布尔值返回结果。伪代码如下:

//伪代码
boolean compareAndSwap(V,A,B){    
            for(;;){        
                if(V==A)
                    V=B;//替换旧值
    }
}
4. CAS方式产生的问题(3个)
  1. ABA问题: CAS操作时,检查值有没有变化,如果没有变化则更新,但是如果一个值原来是A,中间变成了B,然后又变为A,CAS进行检查时,就会发现它的值没有变化,但是实际上却已经变化了。解决ABA问题,可以在变量前加一个版本号,变量更新时,版本号就加1.
  2. 循环时间长,开销大: CAS采用的是自循的方式进行检查,如果长时间不成功,那么就会给CPU带来非常大的开销。
  3. 只能保证一个共享变量的原子操作: 当对一个共享变量进行原子操作时,我们可以采用CAS的方式进行更新,但是如果对多个共享变量进行操作时,CAS就无法保证操作的原子性,那么这个时候就需要用锁来实现。
5. 原子操作类中主要的方法
  • boolean compareAndSet(int expect, int update) ;如果输入的值等于预期值,那么以原子的方式将该值设为输入的值。
  • int addAndGet(int delta);以原子的方式将输入的数值与实例中的值相加,并返回更新之后的值
  • int getAndAdd(int delta); 以原子的方式将输入的数值与实例中的值相加,并返回旧值
  • int getAndSet(int newValue);以原子方式设置为newValue的值,并返回旧值

通过阅读源码,可以发现CAS操作都是使用Unsafe类下的方法进行操作,而Unsafe类只提供了三种CAS方法:

  • compareAndSwapObject(this, valueOffset, expect, update);
  • compareAndSwapLong(this, valueOffset, expect, update);
  • compareAndSwapInt(this, valueOffset, expect, update);

所以,对于其他类型的原子操作,都是进行类型转换,将其类型转换为这三种类型,然后进行原子操作。如Boolean型的,先转成整整,然后在使用compareAndSwapInt进行操作;所以像char/float/double/short…等都可以按照这种思路实现。

原文发布于微信公众号 - 瞎说开发那些事(jsj201501)

原文发表时间:2017-10-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

C# try catch finally

 catch 和 finally 一起使用的常见方式是:在 try 块中获取并使用资源,在 catch 块中处理异常情况,并在 finally 块中释放资源。

1132
来自专栏desperate633

深入理解SortSet类型的使用及应用Redis 有序集合(sorted set)SortSet的应用场景SortSet的常用命令

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

872
来自专栏difcareer的技术笔记

JNI实现源码分析【四 函数调用】正文0x01:dvmCallMethodV0x02:nativeFunc0x03: 何时赋值

有了前面的铺垫,终于可以说说虚拟机是如何调用JNI方法的了。JNI方法,对应Java中的native方法,所以我们跟踪对Native方法的处理即可。

684
来自专栏漏斗社区

代码审计|PHP代码执行函数进阶版实力上线了!

本篇涉及函数:call_user_func()、call_user_func_array(),array_filter,usort,uasort() 0x0...

3014
来自专栏Java帮帮-微信公众号-技术文章全总结

数据结构基础(1) --Swap ; Bubble-Sort ; Select-Sort

Swap的简单实现 //C语言方式(by-pointer): template <typename Type> bool swapByPointer(T...

3308
来自专栏上善若水

P002PHP开发之变量定义

在函数体内定义的global变量,函数体外可以使用,在函数体外定义的global变量不能在函数体内使用,在全局范围内访问变量可以用特殊的 PHP 自定义 $GL...

743
来自专栏PHP技术

PHP字符串和数组操作

*字符串查找 $email = 'name@example@.com'; $domain = strstr($email, '@'); echo $do...

3104
来自专栏李鹏的专栏

Java 虚拟机管理的内存运行时数据区域解释

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的...

820
来自专栏10km的专栏

fastjson:获取类(Class)的所有可序列化字段(field)

用fastjson对一个类进行序列化时,在没有类的实例只有类的Class的情况下,如果想知道这个Class有所有可以被序列化的字段名,可以用com.alibab...

21810
来自专栏微信公众号:Java团长

Java提高篇——对象克隆(复制)

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

1083

扫码关注云+社区