CAS原理浅析——自旋锁ABA情况的解决

分析:
CAS锁 == 自旋锁:一直不断更新,直到没有人进来就更新成功,一直循环,成功为止。(耗CPU、快!快到硬件层面了)

  1. ABA问题:080,看着一样,但是其实不一样了(女朋友例子)
    1 )加版本,versionControl,给别人1回来变成999
    2 )boolean = markable
  2. 保障CAS操作原子性问题:(lock指令),

造成ABA的原因:CAS是乐观锁,需要做很多防御措施(double-lock)

public class BadTest {
    static int val = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 1000; j++) {
            Thread thread = new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    pulsVal();
                }
            });
            thread.start();
        }
        Thread.sleep(500);
        System.out.println("最终val值为:" + val);
        Thread.sleep(500);
        System.out.println("最终val值为:" + val);
    }
    private static void pulsVal() {
        //正是因为有了前面的操作,才导致后面获取不到正确的值,因为与JVM中对val++ 的操作顺序有关
//        TimeUnit.MILLISECONDS.sleep(5);
        val++;
    }
}

在这里插入图片描述
出现了遗漏的现象,因为他们没有排队的机制(sychronized),全部thread抢着进去,导致运算过程获取到中间的值,最终无法得到正确的结果。

CAS模拟实现:val加到1000模拟操作

提高并发性能:降低锁的粒度,只对val进行加锁

public class ModelTest {
    volatile private static int val = 0;
    public static void request() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(5);
        int oldVal;
        while(!compareAndSwap((oldVal = getVal()),oldVal+1));
    }

    /**
     * @Param oldVal是我要改变的值
     * @Param newVal是我要变成的新值,这里进行比较看看能不呢替换,如果确实能替换,
     *        那么就在synchronized区域里面进行替换操作
     * */
    public synchronized static boolean compareAndSwap(int oldVal, int newVal) {
        if (getVal() == oldVal) {
            val = newVal;
            return true;
        }
        return false;
    }
    public static int getVal() {return val;}  //获得实时的值
    
    public static void main(String[] args) throws InterruptedException {
        //开始时间
        long startTime = System.currentTimeMillis();
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);

        for(int i = 0; i < threadSize; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //模拟用户行为,每个用户访问10次网站
                    try {
                        for(int j = 0; j < 10; j++) {
                            request();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            });

            thread.start();
        }
        //怎么保证100个线程 结束之后,再执行后面代码?
        countDownLatch.await();
        long endTime = System.currentTimeMillis();

        System.out.println(Thread.currentThread().getName() + ",耗时:" + (endTime - startTime) + ", count = " + val);
    }
}

ABA的解决——添加版本信息

AtomicStampedReference

两个值变成四个值,加多了版本控制
出现了ABA的情况,如果对ABA不敏感,就无需对它进行额外的升级;解决办法:增加版本机制,

AtomStampReference添加版本信息,reference、stamp
CompareAndSet(期引,新引,期版本,新版本),必须要保证期望的引用和原来的一致,然后才能进入到加锁的版本更新方法中去 ,否则不能够进入更新方法,修改失败。

public class CasABADemo02 {
    public static AtomicStampedReference<Integer> a = new AtomicStampedReference(new Integer(1), 1);

    public static void main(String[] args) {
        Thread main = new Thread(() -> {
                System.out.println("操作线程" + Thread.currentThread().getName() + ", 初始值:" + a.getReference());
                try {

                    Integer expectReference = a.getReference();
                    Integer newReference = expectReference + 1;
                    Integer expectStamp = a.getStamp();
                    Integer newStamp = expectStamp + 1;

                    Thread.sleep(1000);//主线程休眠一秒钟,让出cpu

                    boolean isCASSccuess = a.compareAndSet(expectReference, newReference, expectStamp, newStamp);
                    System.out.println("操作线程" + Thread.currentThread().getName() + ",CAS操作:" + isCASSccuess);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }, "主线程");

        Thread other = new Thread(() -> {
                try {
                    Thread.sleep(20);//确保Thread-main线程优先执行

                    a.compareAndSet(a.getReference(), (a.getReference() + 1), a.getStamp(), (a.getStamp() + 1));
                    System.out.println("操作线程" + Thread.currentThread().getName() + ",【increment】,值=" +a.getReference());
                    a.compareAndSet(a.getReference(), (a.getReference() - 1), a.getStamp(), (a.getStamp() + 1));
                    System.out.println("操作线程" + Thread.currentThread().getName() + ",【decrement】,值=" +a.getReference());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

        }, "干扰线程");

        main.start();
        other.start();
    }
}

在这里插入图片描述
other方法结束后,main方法对值进行操作,虽然值是期望的,但是版本号已经变为3了,所以不满足main方法的期望版本号一致——更新失败。

添加Mark标志信息—— 一次性CAS

AtomicMarkableReference,一次性的锁工具。
如果强行使用,很麻烦需要重量级锁才能保证安全,那样似乎没有实用价值,还是会变为串行化的状态。

//假设开始是没有修改的,那就设置为false
public static AtomicMarkableReference<Integer> amr = new AtomicMarkableReference(1, false);

public static void main(String[] args) {
    Thread main = new Thread(() -> {
        System.out.println("操作线程" + Thread.currentThread().getName() + ", 初始值:" + amr.getReference());
        try {

            Integer expectReference = amr.getReference();
            Integer newReference = expectReference + 1;

            Thread.sleep(1000);//主线程休眠一秒钟,让出cpu
            System.out.println("恢复主线程,假设主线程的其他操作完成……");

            boolean isCASSccuess = amr.compareAndSet(expectReference, newReference, false, true);
            System.out.println("操作线程" + Thread.currentThread().getName() + ",CAS操作:" + isCASSccuess);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "主线程");

    Thread other = new Thread(() -> {
        try {
            Thread.sleep(20);//确保Thread-main线程优先执行

            amr.compareAndSet(amr.getReference(), (amr.getReference() + 1), false,true);
            System.out.println("操作线程" + Thread.currentThread().getName() + ",【increment】,值=" + amr.getReference());
            amr.compareAndSet(amr.getReference(), (amr.getReference() - 1), false,true);
            System.out.println("操作线程" + Thread.currentThread().getName() + ",【decrement】,值=" + amr.getReference());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }, "干扰线程");

    main.start();
    other.start();
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willorn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值