zoukankan      html  css  js  c++  java
  • 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是抢占式的,线程可能在这个原子操作执行结束后被另一个线程所抢占,这样就是导致打印的时候的值不准确。

  • 相关阅读:
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之六 多点触控
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之九 定位
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之七 重力感应
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之五 保存数据的几种方式
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之八 照相机
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之三 Application 配置详解
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之四 打开与关闭应用程序是的保存数据
    ADOBE FLASH BUILDER 4.6 IOS 开发之部署与调试
    [译] 高性能JavaScript 1至5章总结
    页签及盒子的web标准实现
  • 原文地址:https://www.cnblogs.com/ericz2j/p/10289604.html
Copyright © 2011-2022 走看看