深入理解CAS

前言

首先这篇文章是对前文深入理解ConcurrentHashMap中提到的CAS概念做补充的。其次是讲解CAS理论,我也看过很多关于CAS的博客,重复性,概念性都太强了,我要做的与众不同,我会把我所理解的用通俗易懂的语言描述出来的。

正文

什么是CAS

CAS(比较与交换,Compare and swap)是一种有名的无锁算法。

CAS工作原理

CAS指令需要有3个操作数,分别是内存为止(在Java中可以简单理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则他就不执行更新,但是无论是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作。

如何使用CAS操作来避免阻塞同步

下面的代码主要是使用了20个线程进行自增10000次来证明原子性.运行结果是:20000

public static AtomicInteger race = new AtomicInteger(0);
    private static final int THREADS_COUNT = 20;

    public static void increase() {
        race.incrementAndGet();
    }
    @Test
    public void atomicTest() {

        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(race);
    }

我们使用了AtomicInteger了,程序输出正确结果,一切都要归功于incrementAndGet()方法的原子性,该方法无限循环,不断尝试将一个一个比当前值大1的新值赋给自己,如果失败了那说明在执行“获取-设置“操作的时候值已经有了修改,于是再次循环进行下一次操作,只带设置成功为止,它的原理实现其实非常简单。代码如下:

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

上面的核心代码都在Unsafe.class  大家可以自己进去看一看

CAS缺点

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说它值没有被其他线程改变过吗?

如果在这段期间它的值曾经改成了B,后来又改成了A,那么CAS操作就会误认为它没有改变过,这个漏洞称为“ABA”问题。J.U.C包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性,如果需要解决ABA问题,改用传统的互斥同步(典型的就是synchronized 和Lock)可能会比原子类更高效。

总结:Unsafe类是CAS实现的核心。 从名字可知,这个类标记为不安全的,CAS会使得程序设计比较负责,但是由于其优越的性能优势,以及天生免疫死锁(根本就没有锁,当然就不会有线程一直阻塞了),更为重要的是,使用无锁的方式没有所竞争带来的开销,也没有线程间频繁调度带来的开销,他比基于锁的方式有更优越的性能,所以在目前被广泛应用,我们在程序设计时也可以适当的使用.不过由于CAS编码确实稍微复杂,而且jdk作者本身也不希望你直接使用unsafe,所以如果不能深刻理解CAS以及unsafe还是要慎用,使用一些别人已经实现好的无锁类或者框架就好了。

附:

JVM中的CAS

堆中对象的分配

简单的说new出来一个对象之前大小其实已经固定,把他放到堆里以什么形式储存的呢?

由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:

  1. CAS 

实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。

  1. TLAB 

如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。 

虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。

注:对本文有异议或不明白的地方微信探讨,wx:15524579896

猜你喜欢

转载自blog.csdn.net/weixin_38003389/article/details/83343429