线程安全(三)Atomic是什么? Atomic如何保证原子性? Atomic比较案例 图文解释整个CAS过程 Atomic适用场景 Atomic缺点

文章目录

  • 前言
  • AtomicInteger的简单使用
  • Atomic介绍
    • CAS的实现原理
    • 一定会有人疑问,既然++的操作都没有办法保证原子性操作,那么CAS又如何能保证是一个原子性操作?
  • Atomic存在的三个问题

前言

前面两个章节,通过解释线程同步,引入了线程锁synchronized隐式锁。让大家对锁有了深刻的认识。
线程安全(一)synchronized 什么是线程同步?什么是线程安全?什么是线程锁?synchronized怎么用?如何理解wait()和sleep()的区别?超详细例程讲解-------手摸手教会小白
在显式锁lock的章节中,对synchronized和lock介绍了一些对比,通过这样的方式介绍了lock的相关内容,同事介绍了锁的一些其他概念
线程安全(二)Lock 什么是Lock线程锁?与synchronized区别在哪?Lock锁是如何实现等待通知的?如何实现线程顺序执行?
那么,这个章节将要介绍一下Atomic乐观锁。

AtomicInteger的简单使用

首先,通过一个很简单的实例,感受一下AtomicInteger与int 的区别。

public class AtomicRunable implements Runnable {
     
    private static int count;
    private static AtomicInteger atomicInteger = new AtomicInteger(0);
    @Override
    public void run() {
     
        for (int i=0;i<10000;i++){
     
            count++;
            atomicInteger.incrementAndGet();
        }
    }
    public static void main(String[] args) throws InterruptedException {
     
        Thread[] threads = new Thread[1000];
        Runnable runnable = new AtomicRunable();
        for (int i = 0; i < 1000; i++) {
     
            threads[i] = new Thread(runnable) ;
            threads[i].start();
        }
        Thread.sleep(3000);
        System.out.println(count);
        System.out.println(atomicInteger);
    }
}

这个例程是1000个线程,每个线程都对同一个参数做10000次++的操作。

8788841
10000000

从结果上可以看出,使用AtomicInteger之后,无论执行多少次,每次都能够保证原子性。因此Atomic实现了和lock、synchronized同样的目的。

Atomic介绍

在JDK1.5之后,JDK的(concurrent包)并发包里提供了一些类来支持原子操作,如AtomicBoolean,AtomicInteger,AtomicLong等都是用原子的方式来更新指定类型的值。
从多线程并行计算乐观锁 和 悲观锁 来讲,JAVA中的synchronized 属于悲观锁,即是在操作某数据的时候总是会认为多线程之间会相互干扰,属于阻塞式的加锁;Atomic系列则属于乐观锁系列,即当操作某一段数据的时候,线程之间是不会相互影响,采用非阻塞的模式,直到更新数据的时候才会进行版本的判断是否值已经进行了修改,即CAS操作。

CAS的实现原理

假设右边这是很多线程,都想将左边的这个值做+1的操作,右边的这些线程都可以看到左边这个参数的数值。所以这些线程会把看到的值拿来做计算。计算出运算之后值应该是多少,以线程1为例,看到的值为0,要修改为1,其他线程同样如此。
这些线程都要做CAS操作,即compare and change,比较和交换。将0拿去比较,如果左边的参数还是0的话,就换成1。这么多线程,现在都是拿着0去比较,且都要换成1.显然这时只有一个线程能成功
线程安全(三)Atomic是什么? Atomic如何保证原子性? Atomic比较案例 图文解释整个CAS过程 Atomic适用场景 Atomic缺点_第1张图片
假设线程1成功交换,左边的参数被换成1了。此时,其他线程过来做CAS操作时发现,参数已经不再是0了。所以其他线程需要做自旋操作。也就是把新看到的值拿来做计算,然后再次进行cas操作。
线程安全(三)Atomic是什么? Atomic如何保证原子性? Atomic比较案例 图文解释整个CAS过程 Atomic适用场景 Atomic缺点_第2张图片
其他线程自选后如下图所示,所有线程都以这样的方式执行,如果不成功就一直自旋直到成功为止。
线程安全(三)Atomic是什么? Atomic如何保证原子性? Atomic比较案例 图文解释整个CAS过程 Atomic适用场景 Atomic缺点_第3张图片

一定会有人疑问,既然++的操作都没有办法保证原子性操作,那么CAS又如何能保证是一个原子性操作?

所有的线程都是cpu执行的,所以如果在cpu的层面能够确保原子性,那么就可以认为这是个原子性操作。比如cpu提供一个指令就叫做compare and swap 常见的x86 有个指令cmpxchg 支持cas,也就是计算机的硬件支持了这个原子性操作 还有ARM 架构 LL/SC 指令 支持CAS。
所以是CPU层面架构支持,就不需要在操作系统的层面去 那java在Atmoic类中有一个Unsafe属性,unsafe这个类里面有个compareAndSwap方法是个原生的方法在openjdk源码中嵌了汇编代码 cmpxchg 。 所以是在汇编层有这样的硬件指令支持,因此它是原子性的。

Atomic存在的三个问题

序号 问题
1 ABA问题。CAS在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是A,变成B,又变成A,那么CAS进行检查时会认为这个值没有变化,但是实际上却变化了。ABA问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就变成1A-2B-3A。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
2 并发越高,失败的次数会越多,CAS如果长时间不成功,会极大的增加CPU的开销。因此CAS不适合竞争十分频繁的场景。
3 只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

你可能感兴趣的:(线程安全,线程锁,线程安全,多线程,锁,安全,synchronized)