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

猜你喜欢

转载自www.cnblogs.com/ericz2j/p/10289604.html
今日推荐