java中的volatile变量

同步与线程间通信:

    • 通信 
      通信是指消息在两条线程之间传递。 
      既然要传递消息,那接收线程 和 发送线程之间必须要有个先后关系,此时就需要用到同步。通信和同步是相辅相成的。
    • 同步 
      同步是指,控制多条线程之间的执行次序。

线程间通信方式:

  • 共享内存 
    共享内存指的是多条线程共享同一片内存,发送者将消息写入内存,接收者从内存中读取消息,从而实现了消息的传递。 
    但这种方式有个弊端,即需要程序员来控制线程的同步,即线程的执行次序。

这种方式并没有真正地实现消息传递,只是从结果上来看就像是将消息从一条线程传递到了另一条线程。

    • 消息传递 
      顾名思义,消息传递指的是发送线程直接将消息传递给接收线程。 
      由于执行次序由并发机制完成,因此不需要程序员添加额外的同步机制,但需要声明消息发送和接收的代码。

java多线程内存模型:

所有线程都共享一片内存,用于存储共享变量; 
此外,每条线程都有各自的存储空间,存储各自的局部变量、方法参数、异常对象。

volatile的使用:

public volatile boolean flag;

1)volatile在重排序(编译器、处理器在不改变程序执行结果的前提下,重新排列指令的执行顺序,以达到最佳的运行效率)中的使用:

在以下情况下,即使两行代码之间没有依赖关系,也不会发生重排序:

  • volatile读

    • 若volatile读操作的前一行为volatile读/写,则这两行不会发生重排序
    • volatile读操作和它后一行代码都不会发生重排序
  • volatile写
    • volatile写操作和它前一行代码都不会发生重排序;
    • 若volatile写操作的后一行代码为volatile读/写,则这两行不会发生重排序。

volatile保证共享变量的内存可见性:

volatile修饰了一个成员变量后,这个变量的读写就会比普通变量多一些步骤。

  • volatile变量写 
    当被volatile修饰的变量进行写操作时,这个变量将会被直接写入共享内存,而非线程的专属存储空间。
  • volatile变量读 
    当读取一个被volatile修饰的变量时,会直接从共享内存中读,而非线程专属的存储空间中读。

通过对volatile变量读写的限制,就能保证线程每次读到的都是最新的值,从而确保了该变量的内存可见性。

volatile变量只能确保long、double读写的"原子性"(volatile在其他情况下是不能保证原子性的):

在Java中的所有类型中,有long、double类型比较特殊,他们占据8字节(64比特),其余类型都小于64比特。在32位操作系统中,CPU一次只能读取/写入32位的数据,因此对于64位的long、double变量的读写会进行两步。在多线程中,若一条线程只写入了long型变量的前32位,紧接着另一条线程读取了这个只有“一半”的变量,从而就读到了一个错误的数据。 
为了避免这种情况,需要在用volatile修饰long、double型变量.

其实如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。所以说的是线程可见性,没有提原子性。

下面我们用一个例子说明volatile没有原子性,不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁,如i++),仅仅set或者get的场景是适合volatile的。
例如你让一个volatile的integer自增(i++),其实要分成3步:1)读取volatile变量值到local; 2)增加变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:

mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

注意最后一步是内存屏障。
什么是内存屏障(Memory Barrier)?
内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。

内存屏障(memory barrier)和volatile什么关系?上面的虚拟机指令里面有提到,如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

为什么volatile变量用不同的线程访问修改后访问的结果会不一样(多数情况下不建议使用volatile变量提供可见性):

JVM在运行时内存分配汇总有一个内存区域称为虚拟机栈, 线程栈保存了线程运行时的信息,当线程访问某个对象的值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的值load到本地内存中(当前线程所分配内存区域),建立一个变量副本, 之后线程不再和对象在堆内存的变量有任何联系,而是直接修改副本的值,在修改完成之后自动把变量副本写回堆内存,这样堆内存的值就会改变:

read and load: 从主内存复制变量到当前工作内存

use and assign: 执行代码, 改变共享变量

store and write: 用工作内存数据刷新主内存相关内容

但在read and load 之后, 如果线程1对该volatile变量修改还未结束, 线程2也进行修改, 但修改的是最初的值, 将会导致并发的发生.

volatile变量使用的场景:

1).对变量的写入不依赖变量当前的值, 或者能确保只有单线程更新变量的值

2).该变量不会与其他状态变量一起纳入不变性条件中

3).在访问变量时不需要加锁

原文地址:https://www.cnblogs.com/kexianting/p/8504236.html

时间: 2024-10-11 21:37:59

java中的volatile变量的相关文章

java中的Volatile 变量

Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”:与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分.本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形. 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility).互斥即一次 只允许一个线程持

理解java中的volatile关键字

Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了 实现代码线程的安全性.Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分. volatile 写和读的内存语义: 线程 A 写一个 volatile 变量,实质上是线程 A

深入理解Java虚拟机笔记---volatile变量的特殊规则

当一个变量定义成volatile之后,它将具备两种特性:第一是保证此变量对所有线程的可见性,这里的"可见性"是指当一条线程修改了这个变量的值,新值对于其它线程是可以立即得知的,变量值在线程间传递均需要通过主内存来完成,如:线程A修改一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写完成了之后再从主内存进行读取操作,新变量的值才会对线程B可见. 关于volatile变量的可见性,很多人误以为以下描述成立:"volatile对所有线程是立即可见的,对volatil

java中的 Volatile

Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错. Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”:与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是synchronized 的一部分.本文介绍了几

[读书笔记]java中的volatile关键词

以下内容大多来自周志明的<深入理解Java虚拟机>. 当一个变量被volatile修饰后,它将具备两种特性: 1. 保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的.而普通变量不能做到这一点,普通变量的值在线程间传递均需要通过主内容来完成,例如,线程A修改了一个普通变量的值,然后向主内容进行回写,另外一条线程B在线程A回写完成之后再从主内存进行读取操作,新变量值才会对线程B可见. volatile变量在各个线程的工作内存中不存

一定要你明白Java中的volatile

今天Tony来和大家聊聊Java中关键字volatile. 字节码 首先volatile int a = 3;和int a = 3;,加不加volatile关键字,最终生成的字节码都一样的.有兴趣的同学可以试试看看字节码是否一样. 英文解释 Adding volatile to the field does not change Java bytecode that reads or writes the field. It only changes the interpretation of

Java中的静态变量、静态方法、静态代码块

转载自http://www.cnblogs.com/panjun-Donet/archive/2010/08/10/1796209.html (一)静态方法(1)在Java里,可以定义一个不需要创建对象的方法,这种方法就是静态方法.要实现这样的效果,只需要在类中定义的方法前加上static关键字.例如: public static int maximum(int n1,int n2) 使用类的静态方法时,注意:a在静态方法里只能直接调用同类中其他的静态成员(包括变量和方法),而不能直接访问类中的

java中获取环境变量

分为获取java自身的一些环境变量和和操作系统相关的环境变量. 获取JVM相关的一些变量 在运行时设置一个环境变量 debug 为 true: java -Ddebug=true YourClass在程序中设置一个环境变量 debug 为 true: System.setProperty( "debug", "true" );获取一个环境变量 debug : String debug = System.getProperty( "debug" )

java中的环境变量的配置

作为初学者来说配置环境变量是初学者的第一步: 下载并安装JDK1.60(或以上版本),假设安装在X:\jdk150目录下 打开:控制面板 系统 高级 环境变量 变量名 变量值 path .;X:\jdk140\bin ClassPath .;X:\jdk140\lib\tools.jar; .;X:\jdk140\lib\dt.jar; JAVA_HOME X:\jdk140 path 路径是为了保证在任何路径下都能找到,使用java的指令. 配置ClassPath路径是为了虚拟机能够找到我们要