MyException - 我的异常网
当前位置:我的异常网» 移动开发 » Java多线程高并发进阶篇(3)-原子操作的实现原理

Java多线程高并发进阶篇(3)-原子操作的实现原理

www.MyException.Cn  网友分享于:2018-04-18  浏览:0次
Java多线程高并发进阶篇(三)-原子操作的实现原理

要研究原子操作,就必须要对原子操作的来龙去脉有个清晰的认识。我们从原子操作的概念,以及处理器的原子操作和Java中原子操作的实现说起。

一.原子操作的概念

我们在物理学中知道,原子是一个不可再分的最小粒子。同理,原子操作(atomic operation)就是指不可被中断的一个或者一系列操作。

先来了解几个基本概念。


 

二.处理器中如何实现原子操作

在<Java多线程高并发进阶篇(一)-volatile实现原理剖析>中,我们说到了总线锁和缓存锁的概念。没错,在多处理器中,就是使用总线锁和缓存锁来实现处理器之间的原子操作。

首先,我们要明确一点,处理器会自动保证基本的内存操作是原子的(要不然说处理器的内存操作有什么意义),也就是说当一个处理器访问内存中读取一个字节时,其他处理器是不能访问该字节的内存地址的(这是最基本的操作,要不然就乱套了)。

在最新型号的处理器中,都能自动保证处理器对同一个缓存行里的操作是原子的。

这些最基本操作的原子性,是处理器必须要实现的基本功能!

 

那么如何保证在复杂条件下的内存操作原子性(比如跨总线,跨多个缓存行,跨页表等)?

答案就是总线锁和缓存锁。我们再叙述一下这两东西。

1.总线锁

上一篇帖子中已经介绍过,我们复习一遍。

所谓总线锁,就是一个处理器对共享变量进行操作时,会在总线上声言一个LOCK#信号,当其他处理器对共享变量进行操作请求时,就会被阻塞,而该处理器就可以独占共享内存(任我行!)。

举个例子i=1,我们要计算i++。我们预想的结果是3,但是,如果没有总线锁,是不是还能得到3?不一定,可能得到是2。

原因是多个处理器(CPU1,CPU2)同时读取自己缓存行中的i(缓存的都是1),去做操作i++,最后计算完成后都写回到主内存,那么你此时看到的就是2了。

所以,使用总线锁可以保证处理器之间的操作时原子性。

 

2.缓存锁(两个关键词:一个缓存行,缓存一致性协议)

我们在上一帖中也说到过,缓存锁是使用缓存一致性协议来保证处理器之间对缓存行的内存操作的原子性的。

普及一个基础知识,在内存中,频繁使用的数据会被缓存到处理器的一级,二级等高速缓存中。
所谓缓存锁定,就是在一个处理器中,对共享变量的操作不是在主内存中进行操作,而是在处理器自己的缓存中操作。当处理器要对该缓存行进行操作时,就把该缓存行对应的内存地址进行锁定。其他处理器要操作时,会使用嗅探技术进行探测,如果发现该地址被锁定了,那么它就会把自身存着该数据的缓存行设置为无效状态。这就保证了缓存一致性(数据始终保持一致),也就是缓存一致性协议能够阻止缓存了同一缓存行的处理器同时修改。

 

那什么情况下不能使用缓存锁?

一是当操作的数据根本就不能被缓存在处理器内部时候(废话),或者就是要操作的数据跨了多个缓存行(这当然,缓存锁只针对一个缓存行的数据),这时只能使用总线锁。

 

二是处理器不支持缓存锁。比如,Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。

 

Intel处理器中,多个lock前缀指令就使用了上述两种机制实现。例如,位测试和修改令:BTS、BTR、BTC;交换指令XADD、CMPXCHG,以及其他一些操作数和逻辑指令(如ADD、OR)等

 

三.Java中如何实现原子操作?
在Java中,实现原子操作的方法大致有两种。一种是锁机制实现,另外一个就是使用无锁实现--CAS。

1.我们重点说一下无锁实现--CAS.

无锁机制,充分利用了处理器对于原子性的保证机制。CAS在处理器内部使用CMPXCHG完成原子性操作控制。在并发包下的原子实现类中,我们举一例。


在JDK中,Unsafe类的compareAndSwapInt方法的修饰符是Native和final。也就是说,我们需要深入底层,看下这个本地方法的源码实现。这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp,最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp:

// Adding a lock prefix to an instruction on MP machine(添加一条lock前缀指令在MP机器上)
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

 

 

看不懂c++没关系,我们看它的注释,大概能明白:

根据处理器的类型(是单处理器还是多处理器,MP是指Multiple,多个的意思),来决定是否加lock前缀指令.如果是单处理器,LOCK_IF_MP不成立,就不用在cmpxchg指令前加lock前缀指令(注释说VC++不喜欢在单行上加lock前缀指令,实际上单处理器可以自己维护访问一致性,还要lock指令干啥?).如果是多处理器,那么就在在cmpxchg指令前加lock前缀指令.

 

2.CAS中存在的问题

①著名的ABA问题

CAS在操作变量的时候。需要检测变量的值是否发生了变化.如果没有发现变化。它就会以为没有发生过更新操作。但是当我们把值从A-->B,然后B-->A时,其实是发生过变化的。那么如何解决这个问题?

很简单,给每次操作都加一个标志戳(stamp),那么上面的变化过程不是就成了1A->2B-->3A了。

在JDK1.5后,JDK的Atomic包里提供了一个类AtomicStampedReference,来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。


 

②循环时间长

因为我们知道,在原子包中,实现CAS操作都使用了无限循环来进行自旋操作。那么肯定会造成CPU的开销大。


 

③只能保证一个共享变量的原子性操作。从上图我们也可以看到,基本的原子类只能实现对单个共享变量的修改,增加等过程。那么如何解决这个问题呢?从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。  

文章评论

程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
漫画:程序员的工作
漫画:程序员的工作
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序员和编码员之间的区别
程序员和编码员之间的区别
程序员应该关注的一些事儿
程序员应该关注的一些事儿
我的丈夫是个程序员
我的丈夫是个程序员
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
中美印日四国程序员比较
中美印日四国程序员比较
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
程序员必看的十大电影
程序员必看的十大电影
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
我是如何打败拖延症的
我是如何打败拖延症的
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
那些争议最大的编程观点
那些争议最大的编程观点
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
程序员的鄙视链
程序员的鄙视链
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
10个调试和排错的小建议
10个调试和排错的小建议
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
程序员都该阅读的书
程序员都该阅读的书
代码女神横空出世
代码女神横空出世
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
 程序员的样子
程序员的样子
如何成为一名黑客
如何成为一名黑客
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
老程序员的下场
老程序员的下场
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有