1.volatile的应用:

  volatile是轻量级的synchronize,它在多处理器中保证了共享变量的可见性。可见性是表示,用volatile修饰的变量,一个线程修改了共享变量,另一个线程可以读到修改后的数据。如果能够灵活运用volatile,可以减少线程的上下文切换与调度。

2.volatile的原理:

  如果对某个变量使用了volatile修饰进行写操作时,JVM会向处理器发出一条Lock指令,将这个变量的所在缓存行的数据回写到系统内存.这个Lock指令可以独占共享内存,可以称为“”缓存锁定”,不会允许2个处理器同时修改共享变量,如果变量已经回写到缓存内存中,则不会声明Lock,毕竟锁总线的开销较大。但是在多处理器中,如果对应的缓存数据还是旧的,还是无法保证可见性。所以在多处理器中,为了保证数据的可见性,会实现缓存一致性协议。每个处理器通过嗅探总线上的数据去检查自己缓存的数据是否过期,如果发现自己缓存的数据内存地址被修改了,会重新从系统内存中读取数据放到处理器缓存中。

 

3.volatile的使用

  防止重新排序;实现可见性

  volatile只能保证对单次读/写的原子性

可能每个人运行的结果不相同。不过应该能看出,volatile是无法保证原子性的(否则结果应该是1000)。原因也很简单,i++其实是一个复合操作,包括三步骤:

  (1)读取i的值。

  (2)对i加1。

  (3)将i的值写回内存。

volatile是无法保证这三个操作是具有原子性的,我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。

 

大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。

可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。

这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:

假如某个时刻变量inc的值为10,

线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

那么两个线程分别进行了一次自增操作后,inc只增加了1。

解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。