Java语言规范第三版中对volatile定义以下:Java编程语言容许线程访问共享变量,为了确保共享变量可以被准确和一致地更新,线程应该取保经过排它锁单独得到这个变量。Java语言提供了volatile,在某些状况下比锁更方便。若是一个字段被声明成volatile,Java线程内存模型确保全部线程看到这个变量的值是一致的。程序员
一旦一个 共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰以后,那么就具有了两层语义:编程
2.1.1 使用 volatile 关键字会强制 将修改的值当即写入主存;缓存
2.1.2 使用 volatile 关键字的话,当线程 2 进行修改时,会致使线程 1 的量 工做内存中缓存变量 stop 的缓存行无效(反映到硬件层的话,就是 CPU 的 L1或者 L2 缓存中对应的缓存行无效);安全
2.1.3 因为线程 1 的工做内存中缓存变量 stop 的缓存行无效,因此线程 1再次读取变量 stop 的值时 会去主存读取。那么,在线程 2 修改 stop 值时(固然这里包括 2 个操做,修改线程 2 工做内存中的值,而后将修改后的值写入内存),会使得线程 1 的工做内存中缓存变量 stop 的缓存行无效,而后线程 1 读取时,发现本身的缓存行无效,它会等待缓存行对应的主存地址被更新以后,而后去对应的主存读取最新的值。那么线程 1 读取到的就是最新的正确的值。多线程
volatile 关键字禁止指令重排序有两层意思:并发
2.2.1 当程序执行到 volatile 变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且 结果已经对后面的操做 可见;在其后面的操做确定尚未进行。编程语言
2.2.2 在进行指令优化时,不能把 volatile 变量前面的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。性能
为了实现 volatile 的内存语义,加入 volatile 关键字时,编译器在生成字节码时,会在指令序列中插入内存屏障,会多出一个 lock 前缀指令。内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题。编译器和 CPU 能够在保证输出结果同样的状况下对指令重排序,使性能获得优化。处理器在进行重排序时是会考虑指令之间的数据依赖性。优化
内存屏障 有 2 个做用:线程
1)先于这个 内存屏障 的 指令 必须先执行,后于这个 内存屏障的指令 必须后执行 。
2) 使得内存可见性。因此, 若是你的字段是 volatile ,在读指令前插入读屏障,可让高速缓存中的数据失效,从新从主内存加载数据。在写指令以后插入写屏障,能让写入缓存的最新数据写回到主内存。
lock 前缀指令在多核处理器下会引起了两件事情:
1).将当前处理器中这个变量所在缓存行的数据会写回到系统内存。
2)它确保指令重排序时不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操做已经所有完成。
内存屏障能够被分为如下几种类型:
LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2,在 Load2 及后续读取操做要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。
StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2,在 Store2 及后续写入操做执行前,保证 Store1 的写入操做对其它处理器可见。
LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2,在 Store2 及后续写入操做被刷出前,保证 Load1 要读取的数据被读取完毕。
StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2,在 Load2 及后续全部读取操做执行前,保证 Store1 的写入对全部处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
3.1 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所作修改的值)消息。
3.2 线程B读一个volatile变量,实质上是线程B接收了以前某个线程发出的(在写这个volatile变量以前对共享变量所修改的值)消息。
3.3 线程A写一个volatile变量,随后线程B读取这个volatile变量,这个过程实质上是线程A经过主存向线程B发送消息。
4.1 通讯方式的种类
线程之间的通讯一共有两种方式:共享内存 和 消息传递。
共享内存 :指的是多条线程共享同一片内存,发送者将消息写入内存,接收者从内存中读取消息,从而实现了消息的传递。但这种方式有个弊端,即须要程序员来控制线程的同步,即线程的执行次序。这种方式并无真正地实现消息传递,只是从结果上来看就像是将消息从一条线程传递到了另外一条线程。
消息传递: 顾名思义,消息传递指的是发送线程直接将消息传递给接收线程。因为执行次序由并发机制完成,所以不须要程序员添加额外的同步机制,但须要声明消息发送和接收的代码。
Java使用共享内存的方式实现多线程之间的消息传递。所以,程序员须要写额外的代码用于线程之间的同步。
Java 内存模型规定全部的变量都是存在 主存当中,每一个线程都有本身的 工做内存(相似于前面的高速缓存)。线程对变量的全部操做都必须在工做内存中进行,而不能直接对主存进行操做,而且每一个线程不能访问其余线程的工做内存。
Java 内存模型规定全部的变量都是存在 主存当中,每一个线程都有本身的 工做内存(相似于前面的高速缓存)。线程对变量的全部操做都必须在工做内存中进行,而不能直接对主存进行操做,而且每一个线程不能访问其余线程的工做内存。
从图来看,若是线程A与线程B之间要通讯的话,必需要经历下面2个步骤。
1) 线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2) 线程B到主内存中去读取线程A以前已经更新过的共享变量。
答案是否的,若是修改实例变量中的数据,好比i++;也就是i=i+1;则这样的操做其实并非一个原子操做。也就是非线程安全的。表达式i++的操做步骤分解以下:
1)从内存中取出i的值;
2)计算i的值;
3)将i的值写到内存。
假如在第2步计算值的时候,另一个线程也修改i的值,那么这个时候就会出现脏数据。解决的办法使用syschronized关键字。关于syschronized关键字明天继续更新。
1) volatile 是变量修饰符,而 synchronized 则做用于代码块或方法。
2) volatile 不会对变量加锁,不会形成线程的阻塞;synchronized 会对变量加锁,可能会形成线程的阻塞。
3) volatile 仅能实现变量的修改可见性,并不能保证原子性;而synchronized 则 可 以 保 证 变 量 的 修 改 可 见 性 和 原 子 性 。(synchronized 有两个重要含义:它确保了一次只有一个线程能够执行代码的受保护部分(互斥),并且它确保了一个线程更改的数据对于其它线程是可见的(更改的可见性),在释放锁以前会将对变量的修改刷新到主存中)。
4) volatile 标记的变量不会被编译器优化,禁止指令重排序;synchronized 标记的变量能够被编译器优化。
以上内容皆整理自网上,以及《Java并发编程的艺术》,《Java多线程核心编程艺术》,关于今天的更新就到这里啦。