非阻塞式的原子性操作-CAS应用及原理
一:问题抛出
假设在出现高并发的情况下对一个整数变量做依次递增操作,下面这两段代码是否会出现问题?
1.
public class IntegerTest {
private static Integer count = 0;
synchronized public static void increment() {
count++;
}
}
2.
public class AtomicIntegerTest {
private static AtomicInteger count = new AtomicInteger(0);
public static void increment() {
count.getAndIncrement();
}
}
其实在使用Integer的时候,必须加上synchronized保证不会出现并发线程同时访问的情况,而在AtomicInteger中却不用加上synchronized,在这里AtomicInteger是提供原子操作的
二:先看下AtomicInteger类中属性和初始化的一些源码

unsafe:对应的是Unsafe类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。JDK中有一个类Unsafe,它提供了硬件级别的原子操作。JDK API文档也没有提供任何关于这个类的方法的解释。从描述可以了解到Unsafe提供了硬件级别的操作,比如说获取某个属性在内存中的位置,比如说修改对象的字段值,即使它是私有的。
value:volatile修饰的变量,内存中其他线程具有可见性。加或减都是对这个变量值进行修改。
valueOffset:这里指的就是value这个属性在内存中的偏移量(内存中的地址,而不是值),当类被加载时先按顺序初始化static变量和static块,通过unsafe中的public native long objectFieldOffset(Field paramField);
/** Returns the memory address offset of the given static field.
* The offset is merely used as a means to access a particular field
* in the other methods of this class. The value is unique to the given
* field and the same value should be returned on each subsequent call.
* 返回指定静态field的内存地址偏移量,在这个类的其他方法中这个值只是被用作一个访问
* 特定field的一个方式。这个值对于 给定的field是唯一的,并且后续对该方法的调用都应该
* 返回相同的值。
*
* @param field the field whose offset should be returned.
* 需要返回偏移量的field
* @return the offset of the given field.
* 指定field的偏移量
*/
public native long objectFieldOffset(Field field);
获取AtomicInteger类属性value在内存中的偏移量,并将偏移量值赋给valueOffset。需要强调valueOffset代表的不是value值在内存中的位置,而是这个属性在内存中的地址。
三:那么具体看下实现的源码
1.递增的方法:incrementAndGet()

getAndIncrement方法是在一个死循环里面调用compareAndSet方法,如果compareAndSet返回失败,就会一直从头开始循环,不会退出getAndIncrement方法,直到compareAndSet返回true。
2.compareAndSet方法:

AtomicInteger中Unsafe实例调用compareAndSwapInt方法。
3.compareAndSwapInt源码:

看到这里知道是一个本地方法的调用,比较并置换,这里利用Unsafe类的JNI方法实现,使用CAS指令,可以保证读-改-写是一个原子操作。compareAndSwapInt有4个参数,this - 当前AtomicInteger对象,valueOffset- value属性在内存中的位置(需要强调的不是value值在内存中的位置),expect - 预期值,update - 新值,根据上面的CAS操作过程,当内存中的value值等于expect值时,则将内存中的value值更新为update值,并返回true,否则返回false。在这里我们有必要对Unsafe有一个简单点的认识,从名字上来看,不安全,确实,这个类是用于执行低级别的、不安全操作的方法集合,这个类中的方法大部分是对内存的直接操作,所以不安全,但当我们使用反射、并发包时,都间接的用到了Unsafe。
四:并发情况处理流程:
1.首先valueOffset获取value的偏移量,假设value=0,valueOffset=0(valueOffset其实是内存地址,便于表达-后面用valueOffset=n表示对应值的地址)。

2.线程A调用getAndIncrement方法,执行到161行,获取current=0,next=1,准备执行compareAndSet方法
3.线程B几乎与线程A同时调用getAndIncrement方法,执行完161行后,获取current=0,next=1,并且先于线程A执行compareAndSet方法,此时value=1,valueOffset=1
4.线程A调用compareAndSet发现预期值(current=0)与内存中对应的值(valueOffset=1,被线程B修改)不相等,即在本线程执行期间有被修改过,则放弃此次修改,返回false。
5.线程B接着循环,通过get()获取的值是最新的(volatile修饰的value的值会强迫线程从主内存获取),current=1,next=2,然后发现valueOffset=current=1,修改valueOffset=2。
五:总结下AtomicInteger的getAndIncrement方法之所以比普通Integer加减更适用并发环境:
1.current代表value最新的值是因为通过get()方法会从主内存读取(volatile,即读取valueOffset对应的值)
2.能够监测到get()读取到值到cpu执行compareAndSet执行成功之前被别的线程修改成功后的并发情况。
3.上面强调被别的线程“修改成功”是因为假如出现“ABA”情况是不会被觉察的。
即:如果一个变量初次读取的时候是A值,如果在这段期间它的值曾经被改成了B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个漏洞称为CAS操作的"ABA"问题。java.util.concurrent包为了解决这个问题,提供了一个带有标记的原子引用类"AtomicStampedReference",它可以通过控制变量值的版本来保证CAS的正确性。大部分情况下ABA问题并不会影响程序并发的正确性,如果需要解决ABA问题,使用传统的互斥同步可能回避原子类更加高效。
非阻塞式的原子性操作-CAS应用及原理的更多相关文章
- 并发式IO的解决方案:多路非阻塞式IO、多路复用、异步IO
在Linux应用编程中的并发式IO的三种解决方案是: (1) 多路非阻塞式IO (2) 多路复用 (3) 异步IO 以下代码将以操作鼠标和键盘为实例来演示. 1. 多路非阻塞式IO 多路非阻塞式IO访 ...
- Swing做的非阻塞式仿飞秋聊天程序
采用Swing 布局 NIO非阻塞式仿飞秋聊天程序, 切换皮肤颜色什么的小功能以后慢慢做 启动主程序. 当用户打开主程序后自动获取局域网段IP可以在 设置 --> IP网段过滤, 拥有 JMF ...
- 阻塞式和非阻塞式IO
有很多人把阻塞认为是同步,把非阻塞认为是异步:个人认为这样是不准确的,当然从思想上可以这样类比,但方式是完全不同的,下面说说在JAVA里面阻塞IO和非阻塞IO的区别 在JDK1.4中引入了一个NIO的 ...
- Java IO(3)非阻塞式输入输出(NIO)
在上篇<Java IO(2)阻塞式输入输出(BIO)>的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解.现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端 ...
- Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程
Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程 缓冲区(Buffer) 用于存储数据 通道(Channel) 用于传输数据 多路复用器(Selector) 用于轮询 Channel 状 ...
- Java基础——NIO(二)非阻塞式网络通信与NIO2新增类库
一.NIO非阻塞式网络通信 1.阻塞与非阻塞的概念 传统的 IO 流都是阻塞式的.也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在 ...
- 非阻塞式I/O
套接字的默认状态是阻塞的.这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应的操作完成.可能阻塞的套接字调用可分为以下4类 (1)输入操作,包括read,readv,recv ...
- 4.NIO的非阻塞式网络通信
/*阻塞 和 非阻塞 是对于 网络通信而言的*/ /*原先IO通信在进行一些读写操作 或者 等待 客户机连接 这种,是阻塞的,必须要等到有数据被处理,当前线程才被释放*/ /*NIO 通信 是将这个阻 ...
- Linux NIO 系列(03) 非阻塞式 IO
目录 一.非阻塞式 IO 附:非阻塞式 IO 编程 Linux NIO 系列(03) 非阻塞式 IO Netty 系列目录(https://www.cnblogs.com/binarylei/p/10 ...
随机推荐
- pc端常用导航
应为经常要写网站,导航部分基本一样,没必要每次都写一遍,下次直接来复制: HTML: <nav> <div class="clearfix container"& ...
- angularjs 怎么获取鼠标焦点 鼠标移入显示浮动的div提示框
首先,我们要清楚几个基础的知识,angular的两个鼠标移入移出的指令------ng-mouseover(鼠标移入)ng-mouseleave(鼠标移出)--------还有就是window.eve ...
- mysql 的 fiter push down 优化
出处:黑洞中的奇点 的博客 http://www.cnblogs.com/kelvin19840813/ 您的支持是对博主最大的鼓励,感谢您的认真阅读.本文版权归作者所有,欢迎转载,但请保留该声明. ...
- C#语言和SQL Server第十章笔记
第十章 :使用关键字模糊查询 笔记 一:使用关键字 :LIKE BETWEEN IN进行模糊查询 通配符: 一类字符,代替一个或多个真正的字符 与LIKE关键字一起使用 通配符: 解释 实例 符 ...
- Netty之ProtoBuf(六)
Protocol Buffer的基本使用(六) 一.简介 Protocol Buffer(简称ProtoBuf)是google的一个语言中立,平台中立,可扩展的对结构化的数据进行序列化的一种机制,和X ...
- golang 用tar打包文件或文件夹
打包文件用到了tar包,其中tar包的用法可以参考API golang提供了个函数用来遍历文件夹 filepath.Walk 函数具体描述如下: func Walk(root string, walk ...
- mysql一致性读
Consistent Nonlocking Reads 一致读意味着InnoDB用多版本来提供一个查询数据库某个时间点的快照.这种查询可以看到在当前世界点之前事务提交的改变,看不到此后提交的改变,更看 ...
- 在单链表和双链表中删除倒数第k个结点
题目: 分别实现两个函数,一个可以删除单链表中倒数第K个节点,另一个可以删除双链表中倒数第K个节点. 要求: 如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1). 解答: 让链表从头 ...
- [转载]CentOS 7安装Gnome GUI 图形界面
原文链接:http://www.centoscn.com/image-text/config/2015/0528/5552.html 当你安装centos服务器版本的时候,系统默认是不会安装 Cent ...
- 5.For loops
for 循环语句 在需要重复执行代码的时候,for循环常常被用到.我们可以让一行代码执行10次: for i in range(1,11): print(i) 最后一个数字11是不包含在内 ...