设为首页 加入收藏

TOP

Java高性能编程之CAS与ABA及解决方法(三)
2019-09-18 11:10:54 】 浏览:92
Tags:Java 高性能 编程 CAS ABA 解决 方法
白了就是钻了CAS机制的空子。

为了更好地说明这个问题,我们设定两个线程,同时对变量i进行操作。

正常场景:

初始i=0;

线程-1(打算对i进行CAS操作)

线程-1:获取i的旧值-0;

线程-1:设定i的新值-2;

线程-1:对i进行CAS操作,旧值i=0符合实际内存中i现有的值,执行swap操作,i=2;

看似正常的场景:

线程-1(打算对i进行CAS操作)

线程-1:获取i的旧值-0;

线程-2:对i进行了CAS操作,将i改为10;

线程-1:设定i的新值-2;

线程-2:对i进行了CAS操作,将i重新改为0;

线程-1:对i进行CAS操作,旧值i=0符合实际内存中i现有的值,执行swap操作,i=2;

上述的两个场景中线程-1都完成了想要完成的CAS操作,区别就是其中线程2曾经进行过一些操作。

当然这里肯定有朋友要说,这对程序的结果没有任何的影响。是的,在现有的例子中确实对程序的运行结果毫无影响。

这里我举出两个大佬给出的非常经典的例子,分别是极简与复杂的代表。

极简:你从银行取出一箱子钱,放在了车上。结果你一个转头,小偷将装满钱的箱子拿走,并在原来的位置放了一个看起来一模一样,但装满废纸的箱子。你并没有发现这一切,拿着这个箱子开开心心地回家了。囧。

复杂:通过单向链表展现ABA的潜在威胁。由于例子比较复杂,我就不在这里赘述。感兴趣的朋友,可以看看。

其实这两个例子本质都是一样的,想表达的就是我们CAS操作的不是简简单单的数值,更有着其背后的深层信息(然后通过内存,链表,引用来证明观点)。

这里我要开始表达我的观点了:现有的大部分博客或者文章都解释得或多或少有一定问题。。只有部分大佬的博客提到了核心,但是为了说了核心,又举了很多例子(ABA问题的例子本来就不好举,例子大多容易被误解,后面会谈到),导致核心论点被忘却。然后又有很多人去借鉴,或者直接拿来这些例子,但是又不能很好地通过这些例子说明ABA,然后就通过自己的理解解释了一番(更好理解,但是却开始歪了),不断有人进入这个圈子,然后解释越来越歪。造成很多刚了解ABA的小白一脸茫然,看着那套看似正确的解释,就入坑(虽然这个坑影响不那么大,起码对于绝大部分人员都没太大影响。就像很多Java开发者不懂JMM,工作做得不也还可以嘛)了。。。
(这里插句题外话,那就是有关博客抄袭复制的问题。其实我不反对技术的之间的借鉴,毕竟重复造轮子是不可取的,只有有效的思想碰撞才可以产生推动力嘛。但是通过爬虫无脑爬取,或者直接复制粘贴全文,就真的有些过分了。之所以有这种感慨,是因为我现在有时候在百度查询一些资料,十多篇博客看下来,居然大部分都是一样的,太影响效率了)

当然,说话要讲道理的,不能只做“批评家”。就上述两个经典例子存在一个很大的问题,那就是即使脱离了CAS,上述两个例子中存在的问题,还是存在。另外一点佐证就是很多时候,我们需要解决的就是简单的数值的CAS问题,这个数值不牵涉什么复杂的依赖关系。关于这点佐证的最有力说明就是轻量级锁的CAS为什么不需要考虑ABA问题(因为其根本就不涉及什么复杂依赖)。

话说回来,其实上述两个例子,以及我的两点说明,其实更倾向于表现ABA,距离ABA问题的本质,还差了那么一句画龙点睛的总结。

总结:ABA问题的本质就是由于对多线程下CAS流程控制的缺乏,而导致的信息缺失。表现出来的就是由于缺乏必要信息(小偷对箱子进行了操作),而产生了隐患

如果你还是有些无法理解这个结论,那你还记得程序的一个重要原则-程序置于控制下。如果你都无法控制你的程序的行为,那么无疑,你的程序是有问题的。

ABA示例:

接下来通过银行非法洗钱的例子,来简单阐述由信息缺失,造成的问题。

ABATest

这是一个产生了ABA问题的示例。示例中银行无法发现客户账户上的非法洗钱行为。


    package tech.jarry.learning.netease.casWithABA;
    
    import sun.misc.Unsafe;
    import tech.jarry.learning.netease.test.CounterUnsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * @Description:
     * @Author: jarry
     */
    public class ABATest {
    
        volatile int k = 10;
        private static Unsafe unsafe = null;
        private static long valueOffset;
    
        static {
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                unsafe = (Unsafe)field.get(null);
    
                Field iField = CounterUnsafe.class.getDeclaredField("i");
                iField.setAccessible(true);
                valueOffset = unsafe.objectFieldOffset(iField);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        private void transferOld() throws InterruptedException {
            System.out.println("开始转账(旧系统:存在ABA问题)");
            while(true) {
                int current = unsafe.getIntVolatile(this,valueOffset);
                System.out.println("由于CPU抢占问题,转账程序阻塞100ms(为了将可能出现的ABA问题,变成肯定出现)");
                Thread.sleep(100);
                if (unsafe.compareAndSwapInt(this,valueOffset,current,current+1)){
                    System.out.println("银行转账"+1+"元,成功。余额:"+k);
                    break;
                }
                System.err.println("警告:账户存在交易记录以外的资金流动");
            }
        }
    
        private void cleanMoneySub() {
            while(true) {
                int current = unsafe.getIntVolatile(this,valueOffset);
                if (unsafe.compareAndSwapInt(this,valueOffset,current,current-2)){
                    break;
                }
            }
            System.out.println("非法组织洗钱,盗走2元,余额:"+k);
        }
    
        private void cleanMoneyAdd(){
            while(true) {
首页 上一页 1 2 3 4 5 下一页 尾页 3/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇百度地图WEB端判断用户是否在网格.. 下一篇Java性能 -- CAS乐观锁

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目