Atomic底层原理

JDK提供了一些原子操作的类,在java.util.concurrent.atomic下面。如AtomicBoolean,AtomicInteger,AtomicLong都是用原子的方式来更新指定类型的值。

Unsafe类包含了大量多的对C代码的操作,包括了很多直接内存分配和原子操作的调用,都存在安全隐患,所以标记为unsafe。

AtomicInteger是一个标准的乐观锁实现,sun.minc.Unsafe是JDK提供的乐观锁的支持

为什么需要Atomic类?

实例代码

public class Dome01 {
    private Integer count = 0;
   
    private static int min = 10;//每次加10次
    private static int max = 100;//有100个线程同时加
    
    public Integer getCount() {
        return count;
    }
   
    public void addCount(){
        for (int i = 0; i < min; i++) {
            count++;
        }
    }
    @Test
    public void testInteger(){
        Long temp = System.currentTimeMillis();
        Dome01 dome01 = new Dome01();
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < max; i++) {
            executorService.execute(()->{
                dome01.addCount();
            });
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testInteger --- >"+dome01.getCount() +"---- 执行时间 ---- "+(System.currentTimeMillis() - temp));
        executorService.shutdown();
    }

}

执行结果:每次都不一样,往往达不到预期想要的值,而且并发量越大相差的越远。

我们可以看出Integer类型的count在高并发的情况并不能保证线程安全。我们一共执行了1000次,但结果往往是一个小于1000的值。原因在于count++并不是一个原子性操作(读取--运算--赋值)。

如何解决

加锁

synchronized

Lock lock = new ReentrantLock();
public synchronized void addCount(){
    for (int i = 0; i < min; i++) {
        count++;
    }
}

既然是因为count++不是原子操作造成的,那么最容易想到的就是使用synchronized对方法进行加锁,但是我只想保证count++是原子操作,使用synchronized有种高射炮打蚊子的感觉。

ReentrantLock

Lock lock = new ReentrantLock();
public void addCount(){
    for (int i = 0; i < min; i++) {
        lock.lock();
        count++;
        lock.unlock();
    }
}

进一步优化,只对count++操作进行加锁,但是虽然比synchronized性能要好些,但是还有一种杀鸡用牛刀的感觉。

使用CAS

虽然使用synchronized和ReentrantLock都可以解决这个问题,但是总是感觉不够理想,于是引入了CAS。

什么是CAS

CAS是一种乐观锁实现,有三个值 V 表示当前内存的值、A 表示预期的旧值、 B 表示将要更新的新值。

CAS的核心原理在于当要更新值得时候就进行compareAndSwap,而这个compareAndSwap操作是一个native方法,底层是用C/C++写的。最终调用的是cmpxchg方法,其保证了CAS操作的原子性。

compxchg(&a,a,b);//调用compxchg函数
//伪代码
bool cmpxchg(int *v,int a,int b){
    lock();
    if(*v == a){
        *v = b;
        return true;
    }else{//也可以进行自旋
        return false;
    }
    unlock();
}

以上是个人猜测的cmpxchg函数的实现,不然的话,根本无法保证线程安全

假设在多线程环境下,有A,B两个线程同时执行了第一行。同时调用了compxchg(&a,a=10,b=20),由于加了lock只有个线程能执行,线程A判断 if(*v(10) == a(10))成功,然后将*v值修改为20,线程A执行结束释放lock;线程B进入开始执行判断 if(*v(20)==a(10))失败。这样既保证了cas操作的原子性,又保证了线程安全。由于Java没有指针这个函数用Java是实现不了的,这就是C语言的魅力。

CAS解决

现在可以使用CAS来轻量级的解决上面问题。很多复杂的直接对内存的操作Java做不到的,不过JDK提供了一个Unsafe包,里面分装了很多强大的操作。

到此就很优雅的解决了上面提到的count++非原子性的,为了避免重复造轮子,所以JDK将这些CAS操作都分装到了Atomic类中了。

Atomic类有哪些?

Atomic类都放在java.util.concurrent.atomic下面的,如图:前面12个是JDK8之前的,后面5个是JDK8之后的。

基本类型

使用原子的方式更新基本类型

  • AtomicInteger:整形原子类
  • AtomicLong:长整形原子类
  • AtomicBoolean:布尔原子类
数组类型

使用原子的方式更新数组中某个元素

  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray:引用类型数组原子类(数组中存放对象)
引用类型

使用原子的方式更新某个对象

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:AtomicReference的扩展版,增加了一个参数stamp标记,这里是为了解决了AtomicInteger和AtomicLong的操作会出现ABA问题。
  • AtomicMarkableReference :与AtomicStampedReference差不多,只不过第二个参数不是用的int作为标志,而用boolean类型做标记
对象的属性修改类型

使用原子的方式更新某个类中某个字段

  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新长整形字段的更新器
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器

LongAdder

是什么?

LongAdder同样是原子类,AtomicInteger在高并发场景下,会大量自旋带来性能浪费,于是引入了LongAdder类,解决大量自旋的问题。

原理是什么?

由于AtomicInteger类在高并发场景下,会有大量线程要修改同一个值,但同一时刻只有一个线程可以修改成功。其他线程就会自旋来等待。当有大量自旋操作时,是对CPU资源的一种浪费。

LongAdder内部维护了一个Call[]数组,将核心的数据vulue分离到数组的各个位置,每个线程访问时,通过哈希等算法映射到其中一个数字进行运算。而最终的运算结果,则是对Call[]数组中的所有值进行累加求和

总的来说,就是将一个值,分散为不同的值,在并发的时候可以分散压力,在高并发下,可以减少自旋次数,可以显著的提升性能。

ABA问题

举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了,然后觉得不太好,又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。

如何解决ABA问题

可以加一个版本号,每次修改的时候版本号加一,这样就不存ABA的问题了,我在更新之前,除了要确保值没有被修改还要确保版本号没被修改。

思考

我引入了版本号之后,还有必要判断值是原来的值吗?直接根据版本号不就知道了值有木有修改了吗?

个人想法

我们的本意是修改值,重点在于保证值得正确修改,如果把注意点偏移到版本号上,有本末倒置的嫌疑。而且若真的要根据版本号来判断值是否被修改,还需要维护版本号更新的安全性。

原文地址:https://www.cnblogs.com/shaoyu/p/14659980.html