volatile实现原理

volatile实现原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性,即 volatile 保证了 可见性和有序性 没有原子性 。在JVM底层volatile是采用“内存屏障”来实现的。

  • 问题的提出 计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。

有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。举一个简单的例子:

    i++i++
    当线程运行这段代码时,
    首先会从主存中读取i( i = 1),
    然后复制一份到CPU高速缓存中,
    然后CPU执行 + 1 (2)的操作,
    然后将数据(2)写入到告诉缓存中,
    最后刷新到主存中。
    其实这样做在单线程中是没有问题的,有问题的是在多线程中。如下:
    
    假如有两个线程A、B都执行这个操作(i++),按照我们正常的逻辑思维主存中的i值应该=3,但事实是这样么?分析如下:
    
    两个线程从主存中读取i的值(1)到各自的高速缓存中,然后线程A执行+1操作并将结果写入高速缓存中,最后写入主存中,此时主存i==2,线程B做同样的操作,主存中的i仍然=2。所以最终结果为2并不是3。这种现象就是缓存一致性问题。
  • 解决缓存一致性方案有两种:
  1. 通过在总线加LOCK#锁的方式
    通过缓存一致性协议
    但是方案1存在一个问题,它是采用一种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下。

  2. 第二种方案,缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载数据。

一个int变量,用volatile修饰,多线程去操作++,线程安全吗?

    不安全。volatile只能保证可见性,并不能保证原子性。
    i++实际上会被分成多步完成:
    1)获取i的值;
    2)执行i+1;
    3)将结果赋值给i。
    volatile只能保证这3步不被重排序,多线程情况下,可能两个线程同时获取i,执行i+1,然后都赋值结果2,实际上应该进行两次+1操作。

那如何才能保证i++线程安全?

可以使用java.util.concurrent.atomic包下的原子类,如AtomicInteger。
其实现原理是采用CAS自旋操作更新值。CAS即compare and swap的缩写,中文翻译成比较并交换。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。自旋就是不断尝试CAS操作直到成功为止。

CAS实现原子操作会出现什么问题?

ABA问题。因为CAS需要在操作之的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成,有变成A,那么使用CAS进行检查时会发现它的值没有发生变化,但实际上发生了变化。ABA问题可以通过添加版本号来解决。Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
循环时间长开销大。pause指令优化。
只能保证一个共享变量的原子操作。可以合并成一个对象进行CAS操作。

你可能感兴趣的:(Android进阶)