Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AtomicMarkableReference 不能解决ABA问题 #626

Closed
itmaaa opened this issue Jan 17, 2020 · 8 comments
Closed

AtomicMarkableReference 不能解决ABA问题 #626

itmaaa opened this issue Jan 17, 2020 · 8 comments

Comments

@itmaaa
Copy link

itmaaa commented Jan 17, 2020

/**

  • AtomicMarkableReference则是将一个boolean值作是否有更改的标记,本质就是它的版本号只有两个,true和false,

  • 修改的时候在这两个版本号之间来回切换,这样做并不能解决ABA的问题,只是会降低ABA问题发生的几率而已

  • @author : mazh

  • @Date : 2020/1/17 14:41
    */
    public class SolveABAByAtomicMarkableReference {
    private static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100,false);

    public static void main(String[] args) {

     Thread refT1 = new Thread(() -> {
         try {
             TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         atomicMarkableReference.compareAndSet(100, 101, atomicMarkableReference.isMarked(), !atomicMarkableReference.isMarked());
         atomicMarkableReference.compareAndSet(101, 100, atomicMarkableReference.isMarked(), !atomicMarkableReference.isMarked());
     });
    
     Thread refT2 = new Thread(() -> {
         boolean marked = atomicMarkableReference.isMarked();
         try {
             TimeUnit.SECONDS.sleep(2);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         boolean c3 = atomicMarkableReference.compareAndSet(100, 101, marked, !marked);
         System.out.println(c3); // 返回true,实际应该返回false
     });
    
     refT1.start();
     refT2.start();
    

    }
    }

个人理解是这样,不知是否正确?

@jinyahuan
Copy link
Contributor

。。。刚看到这个issue。你的用法不对,mark只是用来标记是否cas过的,正常情况下别重新设置值,除非要循环使用。稍后我根据你的案例写个用法。

@jinyahuan
Copy link
Contributor

jinyahuan commented Mar 9, 2020

之前理解错了,重新写了份案例。

import java.util.concurrent.atomic.AtomicMarkableReference;

public class AtomicMarkableReferenceDemo {
    private static final boolean DEFAULT_MARK_FLAG = false;
    private static AtomicMarkableReference amr = new AtomicMarkableReference(100, DEFAULT_MARK_FLAG);

    public static void main(String[] args) {
        Thread refT2 = new Thread(() -> {
            final boolean marked = amr.isMarked();
            final Integer reference = (Integer) amr.getReference();
            System.out.println("refT2 currentValue=" + amr.getReference() + ", currentMark=" + marked);

            // 这段目的:模拟处理其他业务花费的时间
            safeSleep(300);

            // 别的线程操作时,根据匹配之前的(值、mark)和最新的(值、mark),如果同值,且 mark 不为默认值就是执行过了
            if (reference.equals(amr.getReference())
                    && amr.isMarked() == DEFAULT_MARK_FLAG) { // fixed: 最新的 mark 为 默认值时才进行 cas

                System.out.println("refT2 currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked()
                        + ", oldValue=" + reference + ", oldMark=" + marked);
                boolean casResult = amr.compareAndSet(reference, reference + 1, DEFAULT_MARK_FLAG, !DEFAULT_MARK_FLAG);
                System.out.println("refT2 cas result --------> " + casResult);
            }
            System.out.println("refT2 ending ...");
        });

        refT2.start();
        // 这段目的:为了让 refT2 线程先跑起来
        safeSleep(100);

        Thread refT1 = new Thread(() -> {
            // 同个线程操作时设置 mark 为 默认值的取反,且之后不要改变

            final Integer reference = (Integer) amr.getReference();
            System.out.println("refT1 currentValue=" + reference + ", currentMark=" + amr.isMarked());
            boolean casResult = amr.compareAndSet(reference, reference + 1, DEFAULT_MARK_FLAG, !DEFAULT_MARK_FLAG);
            System.out.println("refT1 cas result --------> " + casResult);

            final Integer reference2 = (Integer) amr.getReference();
            final boolean marked = amr.isMarked();
            System.out.println("refT1 currentValue=" + reference2 + ", currentMark=" + marked);
            boolean casResult2 = amr.compareAndSet(reference2, reference2 - 1, marked, marked);
            System.out.println("refT1 cas2 result --------> " + casResult2);

            System.out.println("refT1 currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
            System.out.println("refT1 ending ...");
        });

        refT1.start();
    }

    private static void safeSleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

理论上输出是这样的:

refT2 currentValue=100, currentMark=false
refT1 currentValue=100, currentMark=false
refT1 cas result --------> true
refT1 currentValue=101, currentMark=true
refT1 cas2 result --------> true
refT1 currentValue=100, currentMark=true
refT1 ending ...
refT2 ending ...

@itmaaa
Copy link
Author

itmaaa commented Mar 18, 2020

@jinyahuan @Snailclimb hello,看了你的代码,我们之间的理解有点误差,我分析下我的用法,也欢迎大家参与讨论。
我原先代码中的 refT1 将100改成101 又将101改成100模拟的是两个线程的操作,不是同一个线程的操作,只是为了方便测试数值更改的先后顺序。对于AtomicMarkableReference 作为一个全局变量,我的理解是可以复用的,意味着每个方法一进来就先获取 mark标记,方法结束去更新时再判断 mark标记是否与刚才获取的一致(在内存值相同的情况下),更新时将标记取反,这样看似每次都可以复用(在奇数次取反后标记不匹配)。我例子的展示的是假设 refT2 一执行就获取标记,更新时前面已经有两个线程完成两次标记取反(偶数次),所以又匹配上了导致没有解决ABA的问题。对于你说的mark只是用来标记是否cas过的,正常情况下别重新设置值,我们不会在第一次成功执行mark取反后以后都不再重置吧,那样在多线程下我们怎么复用?

@jinyahuan
Copy link
Contributor

jinyahuan commented Mar 18, 2020

@itmaaa 常规情况下是不会做出ABA操作的,往往都是远离原值变化的(就像AtomicInteger一样,用到的都是incrementAndGet),不会改变了原值之后还还原的。

当然主要还是生产中没遇到过有ABA操作的情况,只能靠模拟,我也不能说这样做就是合适的

如果非要在多线程中使用的话,你的话也让我有了灵感:设定特定的匹配规则来绑定mark,比如说reference的值或hashcode的奇偶性绑定true或false,来表示是否能够继续操作,关联成功就能继续操作。这样理论上可以实现循环使用。有想法的话可以试试看可行不可行。

比如:偶数绑定mark中的true,奇数绑定mark中的false。当匹配到原值,且奇偶数与mark也匹配成功,那么就能进行cas。

@jinyahuan
Copy link
Contributor

jinyahuan commented Mar 18, 2020

上面的方案感觉还是不可行(变回原值后hashcode也应该会相同),等下给划掉,可能还是太菜了- -

这里再给一种方案,理论上所有Atomic类都适用,加上一个 volatile long 值的 modCount,类似于 fail-fast机制。

不过这种机制就是AtomicStampedReference的变种,没啥意义。

@itmaaa
Copy link
Author

itmaaa commented Mar 18, 2020

@jinyahuan 现实场景中会不会做出ABA操作主要还是看业务的,如果把我例子看成银行转账,100改101看成入账,101改100看成扣款,对于refT2 去操作时发现还是100(标记也符合)就改成101了,解决ABA问题主要在于保证获取标记后到更新期间不被更改,至于出现ABA问题会不会对业务造成影响(需不需要解决),还是看实际场景的,我个人这么理解

@jinyahuan
Copy link
Contributor

@jinyahuan 现实场景中会不会做出ABA操作主要还是看业务的,如果把我例子看成银行转账,100改101看成入账,101改100看成扣款,对于refT2 去操作时发现还是100(标记也符合)就改成101了,解决ABA问题主要在于保证获取标记后到更新期间不被更改,至于出现ABA问题会不会对业务造成影响(需不需要解决),还是看实际场景的,我个人这么理解

恩,看实际应用场景。AtomicMarkableReferenceAtomicStampedReference都有自己适用的情况。

@supervate
Copy link

AtomicMarkableReference 我认为它压根就没想解决ABA问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants