• Java多线程_CAS算法和ABA问题


    CAS算法概述
    CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

    CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

     

    注:t1,t2线程是同时更新同一变量56的值

    因为t1和t2线程都同时去访问同一变量56,所以他们会把住内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程值都为56

    假设t1和t2在线程竞争中线程t1能去更新变量值改为57,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。T1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值改为57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。

    CAS算法的开销主要来自Cache Miss,一旦导致CAS一直更新失败的话,它的性能是有可能坏于加锁的方式的。

    ABA问题概述

    上面我我们说了CAS算法,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。关于“ABA”问题,我们假设如下事件序列:

    线程 1 从内存位置V中取出A。
    线程 2 从位置V中取出A。
    线程 2 进行了一些操作,将B写入位置V。
    线程 2 将A再次写入位置V。
    线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。
    尽管线程 1 的CAS操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。

    我们形象地画一个图来打个比方:

    解决方法
    我们在AtomicReference的使用中就遇到了这样的ABA问题,name怎么解决的呢?我们使用AtomicStampedReference就能很好的解决这个问题了,首先,我们先看一下这一段代码(实现自动充值,当少于20元时,充值20元,再进行消费,每次消费10元):

    import java.util.concurrent.atomic.AtomicStampedReference;
    public class AtomicReferenceDemo {
        static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 1);
        static Object obj = new Object();
    
        public static void main(String[] args) {
            Add add = new Add();
            Thread thread1 = new Thread(add);
            thread1.start();
            Reduce reduce = new Reduce();
            Thread thread2 = new Thread(reduce);
            thread2.start();
        }
    
        static class Add implements Runnable {
    
            @Override
            public void run() {while (true) {
                        Integer m = money.getReference();
                        synchronized (obj) {                   // 1处
                            if (m < 20) {
                                if (money.compareAndSet(m, m + 20, money.getStamp(), money.getStamp() + 1)) {   //2处  
                                    System.out.println("充值成功,余额:" + money.getReference() + "元");  
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
            }
    
        static class Reduce implements Runnable {
    
            @Override
            public void run() {
                while (true) {
                    while (true) {
                        Integer m = money.getReference();
                        synchronized (obj) {                   //3
    
                            if (m > 10) {
                                if (money.compareAndSet(m, m - 10, money.getStamp(), money.getStamp() + 1)) {     //4处
                                    System.out.println("成功消费10元,余额:" + money.getReference() + "元");
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    我们在1处2处加上锁之后就OK了!这是结果:

    这样问题就很好的解决了,不过特别注意的是在1处和3处需要加锁,因为2处和4处的if条件是一个原子操作,大家都知道,java是抢占式的,线程可能在这个原子操作执行结束后被另一个线程所抢占,这样就是导致打印的时候的值不准确。

  • 相关阅读:
    闭包函数与装饰器
    python 函数的参数
    python 函数定义及调用
    python 文件指针及文件覆盖
    phtnon 文件操作
    Volatile 关键字 内存可见性
    UNION 和 UNION ALL 操作符
    设计模式(七)---- 模板方法模式
    设计模式(六)---- 策略模式
    设计模式(五)---- 原型模式
  • 原文地址:https://www.cnblogs.com/ericz2j/p/10289604.html
Copyright © 2020-2023  润新知