非阻塞式的原子性操作-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 ...
随机推荐
- 阿里云ECS升级OpenSSL记录
1.下载OpenSSL wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz 2.解压编译安装 tar xf openssl-1.1.0e ...
- Web API系列之一 Rest简介
1.REST:Representational State Transfer表征状态转移,是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格.REST设计风格有如下几点: ...
- 点击盒子选中里面的单选框,并给盒子添加相应样式,美化单选框、复选框样式css用法,响应式滴
pc效果图: 移动端效果图: 代码直接上: <!DOCTYPE html> <html> <head> <meta http-equiv="Cont ...
- Nginx集群之基于Redis的WebApi身份验证
目录 1 大概思路... 1 2 Nginx集群之基于Redis的WebApi身份验证... 1 3 Redis数据库... 2 4 Visualbox ...
- 如何在开发时部署和运行前后端分离的JavaWeb项目
在开发中大型的JavaEE项目时,前后端分离的框架逐渐成为业界的主流,传统的单机部署前后端在同一个项目中的工程项目越来越少.这类JavaWeb项目的后端通常都采用微服务的架构,后端会被分解为诸多个小项 ...
- IDEA第七章----插件
idea的另一个可爱之处,就是它的强大的插件,下面我以CodeGlance插件为例,这个可以快速定位代码 第一节:安装插件 ● All plugins 显示所有插件. ● Enabled 显示当前所有 ...
- C#Winform 自定义透明按钮和单窗体模块化实现
技术看点 WinForm自定义控件的使用 WinForm单窗体应用如何模块化 需求及效果 又来一波 C# GDI自定义控件show .这个控件已经使用几年了,最近找出来重构一下.原来是没有边框的,那么 ...
- 《高性能javascript》学习总结
本文是学习<高性能javascript>(Nichols C. Zakes著)的一些总结,虽然书比较过时,里面的知识点也有很多用不上了,但是毕竟是前人一步步探索过来的,记录着javascr ...
- Vista 及后续版本的新线程池
在上一篇的博文中,说了下老版本的线程池,在Vista之后,微软重新设计了一套线程池机制,并引入一组新的线程池API,新版线程池相对于老版本的来说,它的可控性更高,它允许程序员自己定义线程池,并规定线程 ...
- Mycat 配置
前言 Mycat 是一个数据库分库分表中间件 MyCAT 是作为通用代理设计的,后端是以 Mysql协议 和 JDBC 的方式连接数据库,可以支持 Oracle.DB2.SQL Server . mo ...