前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

1、CAS算法

在java中锁分为乐观锁和悲观锁。

1、悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。

2、乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。

CAS是compare and swap的缩写,即我们所说的比较交换。CAS是一种基于锁的操作,(又称为无锁操作)是一种乐观锁策略

1.1、原理

CAS 操作包含三个操作数 —— 内存位置(V)、预期值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,当多个线程使用CAS操作一个变量是,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试(我们自己设置的哦),直到没有冲突为止,当然也可以选择跳过或者,或者挂起线程

1.1.1、AtomicLong 原子性操作类

1.1.1.1、不使用synchronize,使用原子性操作类AtomicLong

public class _1_AtomicMethod {

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            Thread thread = new Thread(() -> {
                if (Counter.addOne() == 1000) {
                    System.out.println("counter = 1000");
                }
            });
            thread.start();
        }
    }

}


class Counter {
    private static AtomicLong atomicLong = new AtomicLong(0);

    public static long addOne() {
        return atomicLong.incrementAndGet();
    }
}

1.1.1.2、atomicLong.incrementAndGet方法解析

incrementAndGet调用getAndAddLong方法

getAndAddInt方法解析:拿到内存位置的最新值v,使用CAS尝试修将内存位置的值修改为目标值v + delta,如果修改失败,则获取该内存位置的新值v,然后继续尝试,直至修改成功

value:使用volatile修饰保证了可见性

private volatile long value;

public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

getAndAddLong调用compareAndSwapLong即本文的主角CAS

unsafe.compareAndSwapInt(this, valueOffset, expect, update); CAS(Compare And Swap),意思是如果valueOffset位置包含的值与expect值相同,则更新valueOffset位置的值为update,并返回true,否则不更新,返回false。

public final long getAndAddLong(Object o, long offset, long delta) {
    long v;
    do {
        v = getLongVolatile(o, offset);
    } while (!compareAndSwapLong(o, offset, v, v + delta));
    return v;
}

1.2、CAS问题

1.2.1、ABA问题

因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,所以继续执行了,但是实际上的确发生了变化。

不容易看出问题的主要还是因为:“值是一样的”等同于“没有发生变化”(就算被改回去了,那也是变化)的认知。毕竟在大多数程序代码中,我们只需要知道值是不是一样的,并不关心它在之前的过程中有没有发生变化;所以,当我需要知道之前的过程中“有没有发生变化”的时候,ABA就是问题了。

解决方案:可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。 1A——2B——3A 即使数据仍然是A,但是最后一步A的版本号为3,1A≠3A,因此可以避免ABA的问题

1.2.2、自旋时间过长

使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。

1.2.3、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时CAS能保证其原子性,如果对多个共享变量进行操作,CAS就不能保证其原子性。有一个解决方案是利用对象整合多个共享变量即一个类中的成员变量就是这几个共享变量。然后将这个对象做CAS操作就可以保证其原子性。atomic中提供了AtomicReference来保证引用对象之间的原子性。

ContactAuthor