对volatile的一点lijie

  理解volatile其实还是有点儿难度的,它与Java的内存模型有关,所以在理解volatile之前需要先了解有关Java内存模型的概念,目前只做初步的介绍。

一、操作系统语义

  计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。

  我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。

  CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。

1、产生数据一致性 / 内存不可见性问题

  有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:内存不可见性 ——> 数据一致性。

  线程在运行的过程中会把主内存的数据拷贝一份到线程内部cache中,也就是working memory。这个时候多个线程访问同一个变量,其实就是访问自己的内部cache,不再与主存打交道,只有当运行结束后才会将数据刷新到主存中。

  【举一个简单的例子】:

public class VariableTest {

    public static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        new Thread(threadA, "threadA").start();
        Thread.sleep(1000l);//为了保证threadA比threadB先启动,sleep一下
        new Thread(threadB, "threadB").start();

    }

    static class ThreadA extends Thread {
        public void run() {
            while (true) {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + " : flag is " + flag);
                    break;
                }
            }

        }

    }

    static class ThreadB extends Thread {
        public void run() {
            flag = true;
            System.out.println(Thread.currentThread().getName() + " : flag is " + flag);
        }
    }
}

  运行结果:(光标一直在闪)

  上面例子出现问题的原因在于:

      (1)线程A把变量flag(false)加载到自己的内部缓存cache中;线程B修改变量flag后,即使重新写入主内存,但是线程A不会重新从主内存加载变量flag,看到的还是自己cache中的变量flag。所以线程A是读取不到线程B更新后的值,然后一直死循环...

(2)除了cache的原因,重排序后的指令在多线程执行时也有可能导致内存不可见,由于指令顺序的调整,线程A读取某个变量的时候线程B可能还没有进行写入操作呢,虽然代码顺序上写操作是在前面的。

2、解决缓存一致性方案

  1. 通过在总线加LOCK#锁的方式
  2. 通过缓存一致性协议

  但是方案1存在一个问题:它是采用一种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下。

  第二种方案:缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。

        其核心思想如下:当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载

                数据。  

二、Java内存模型

1、共享变量

     共享变量是指:可以同时被多个线程访问的变量,共享变量是被存放在堆里面,所有的方法内的临时变量都不是共享变量。

2、重排序

  重排序是指为了提高指令运行的性能,在编译时或者运行时对指令执行顺序进行调整的机制。重排序分为编译重排序和运行时重排序。

  编译重排序是指:编译器在编译源代码的时候就对代码执行顺序进行分析,在遵循as-if-serial的原则前提下对源码的执行顺序进行调整。

  运行时重排序:是指为了提高执行的运行速度,系统对机器的执行指令的执行顺序进行调整。

 【as-if-serial原则】是指在单线程环境下,无论怎么重排序,代码的执行结果都是确定的。

3、可见性

  内存的可见性是指线程之间的可见性,一个线程的修改状态对另外一个线程是可见的,用通俗的话说,就是假如一个线程A修改一个共享变量flag之后,则线程B去读取,一定能读取到最新修改的flag。

(看上边示例)  

  Java提供了volatile来保证可见性。

  当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,当其他线程读取共享变量时,它会直接从主内存中读取。

  当然,synchronize和锁都可以保证可见性。

4、原子性

  原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。就像数据库里面的事务一样,他们是一个团队,同生共死。

  在单线程环境下我们可以认为整个步骤都是原子性操作,但是在多线程环境下则不同,Java只保证了基本数据类型的变量和赋值操作才是原子性的。

  想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。

// 一个很经典的例子就是银行账户转账问题:
      比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。试想一下,如果这2个操作不具备原子性,会造成什么样的后果。
      假如从账户A减去1000元之后,操作突然中止(B此时并没有收到)。
      然后A又从B取出了500元,取出500元之后,再执行 往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元(少了500)。

      所以这2个操作必须要具备原子性(不可分割的最小操作单位)才能保证不出现一些意外的问题。  

三、volatile原理

  (1)volatile修饰的变量不允许线程内部cache缓存和重排序,可以保证内存的可见性和数据的一致性。

   (2)线程读取数据的时候直接读写内存,同时volatile不会对变量加锁,因此性能会比synchronized好。

  另外还有一个说法是使用volatile的变量依然会被读到线程内部cache中,只不过当B线程修改了flag后,会将flag写回主内存,同时会通过信号机制通知到A线程去同步内存中flag的值。

 volatile相对于synchronized稍微轻量些,在某些场合它可以替代synchronized,但是又不能完全取代synchronized,只有在某些场合才能够使用volatile。 使用它必须满足如下两个条件:
// 1、对变量的写操作不依赖当前值;
// 2、该变量没有包含在具有其他变量的不变式中
 volatile经常用于两个两个场景:状态标记、double check(单例模式)

原文地址:https://www.cnblogs.com/timetellu/p/11618680.html

时间: 2024-08-10 00:21:51

对volatile的一点lijie的相关文章

关于synchronized与volatile的一点认识

贪婪是一种原罪,不要再追求性能的路上离正确越来越远. 内存模型 java内存模型 重排序 锁synchronized 什么是锁 独占锁 分拆锁 分离锁 分布式锁 volatile 内存模型 java内存模型 提到同步.锁,就必须提到java的内存模型,为了提高程序的执行效率,java也吸收了传统应用程序的多级缓存体系. 在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调.在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherence),其中一部

volatile的一点理解

对于volatile的理解,我想通过代码来表达. p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco; color: #931a68 } p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco; min-height: 25.0px } p.p

volatile

volatile 可以看做是一种轻量级的synchronized实现,volatile实现的仅仅是synchronized的部分功能,但是开销较synchronized小.特定的情形下,使用volatile会更为合适. synchronized提供了两种主要特性:互斥 和可见性.互斥即同一时刻只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据.可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见

C#SpinWait和volatile一点温习

今天看ConcurrentQueue<T> 源码发现里面居然没有用到lock,我记得ConcurrentDictionary里面是有lock的,lock的是字典里面每一个key,但是ConcurrentQueue<T> 的线程安全确是用SpinWait对象和volatile关键字来实现,于是乎就温习了一下,直接上code class Test { /* volatile多用于多线程的环境,当一个变量定义为volatile时,读取这个变量的值时候每次都是从momery里面读取而不是从

就是要你懂Java中volatile关键字实现原理

原文地址http://www.cnblogs.com/xrq730/p/7048693.html,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好.更正确地地

Java并发编程:volatile关键字解析(转)

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola

Java并发编程:volatile关键字解析

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola

volatile关键字解析

转载:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来

6、Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil