一、java内存模型

1.java内存模型

程序运行过程中的临时数据是存放在主存(物理内存)中,但是现代计算机CPU的运算能力和速度非常的高效,从内存中读取和写入数据的速度跟不上CPU的处理速度,在这种情况下,CPU高速缓存应运而生。基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是出现了一个新的问题:目前计算机多为多核CPU或多处理器,每个处理器都有自己的高速缓存,但是这些处理器又共享同一主存。java内存模型主要是定义了java程序中各个变量的访问规则,这里的变量与java编程中变量不是同一个概念,包括:实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,java内存模型规定:

(1)所有的变量存储在主内存中,每个线程都有自己的工作内存,线程的工作内存保存了该线程使用到的变量,并且这些变量是从主内存中对应变量的拷贝

(2)线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量

(3)不同线程之间无法直接访问彼此的工作内存,线程间的变量值的传递需要借助主内存。

2.java工作内存与主内存的交互

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

二、基本概念

1.可见性:java多线程之间的可见性,一个线程修改的某个对象的状态对另外一个线程是可见的。java中经常使用同步或者volatile来保证变量的可见性。

2.原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子代表不可分割性,例如:int a=0,这个操作是不可分割的,反之,a++这个操作是可以切割的
可以分为a=a+1,读取内存中a的值,读取a的值后进行加1操作,把结果值写入内存。

3.有序性:即程序执行的顺序按照代码的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

4.重排序:在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序,重排序分成三种类型:

  • 编译器优化的重排序。编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序

  • 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行
  • 内存系统的重排序。由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行

三、volatile

1.基本概念

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

2.基本使用

 public class DoubleCheck{

     private static volatile DoubleCheck instance;

     private DoubleCheck(){}

     public static DoubleCheck getInstance(){

         //第一次检测
if (instance==null){
//同步
synchronized (DoubleCheck.class){
if (instance == null){
//多线程环境下可能会出现问题的地方
instance = new DoubleCheck();
}
}
}
return instance;
}
}

上述代码一个经典的单例的双重检测的代码,在单线程环境下这样写并没有什么问题,但如果在多线程环境下就可能出现线程安全问题。原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化,而通过volatile修饰后,就能很好的解决非线程安全的问题。

3.总结

①、保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新

②、禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障

③、volatile的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

④、volatile变量,本质上是通过内存屏障来实现其可见性和禁止重排优化。内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。内存屏障有两个作用:阻止屏障两侧的指令重排序;强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

Java并发基础--volatile关键字的更多相关文章

  1. 一起来看看java并发中volatile关键字的神奇之处

    并发编程中的三个概念: 1.原子性 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行. 2.可见性 对于可见性,Java提供了volati ...

  2. Java 并发:volatile 关键字解析

    摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...

  3. java并发系列(六)-----Java并发:volatile关键字解析

    在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...

  4. Java并发编程 Volatile关键字解析

    volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...

  5. java并发:volatile关键字

    java并发需要保证原子性,可见性,有序性. http://www.cnblogs.com/expiator/p/9226775.html 一.volatile关键字作用如下: 1.volatile关 ...

  6. Java并发编程volatile关键字

    volatile理解 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和volatile 关键字机制.volatile具有synchronized关键字的“可见性”,vo ...

  7. java并发编程 volatile关键字 精准理解

    1.volatile的作用 一个线程共享变量(类的成员变量.类的静态成员变量等)被volatile修饰之后,就具有以下作用: 1)并发中的变量可见性(不同线程对该变量进行操作时的可见性),即一个线程修 ...

  8. Java 并发编程——volatile与synchronized

    一.Java并发基础 多线程的优点 资源利用率更好 程序设计在某些情况下更简单 程序响应更快 这一点可能对于做客户端开发的更加清楚,一般的UI操作都需要开启一个子线程去完成某个任务,否者会容易导致客户 ...

  9. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

随机推荐

  1. 简单说一说对JavaScript原型链的理解

    每一个JavaScript对象都和另一个对象相关联,相关联的这个对象就是我们所说的“原型”.每一个对象都会从原型继承属性和方法.有一个特殊的对象没有原型,就是Object,还有一种通过Object.c ...

  2. qt+vs2005新建配置不自动加载Generated Files进工程(个人备份)

    工程右键Qt Project Settings 的Moc Directory路径删除 确定,再进入将删除路径加上

  3. #leetcode刷题之路15-三数之和

    给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. ...

  4. 复习宝典之MyBatis

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第五章:MyBatis MyBatis是一个可以自定义SQL.存储过程和高级映射的持久层框架. 1)创建sqlsession的流程 my ...

  5. Git 远程推送被拒绝的一种解决方案

    今天在推送的时候发生了如下错误信息: error: 无法推送一些引用到 'https://gitee.com/von_w/demo_app.git'提示:更新被拒绝,因为您当前分支的最新提交落后于其对 ...

  6. git获取步骤

    $ git init $ git config --global user.name "[name]" $ git config --global user.email [emai ...

  7. java实现验证码功能主要代码

    package com.baojuan.servlet; import java.awt.Color;import java.awt.Font;import java.awt.Graphics2D;i ...

  8. Vue清除所有JS定时器

    Vue清除所有JS定时器 在webpack + vue 的项目中如何在页面跳转的时候清除所有的定时器 JS定时器会有一个返回值(数字),通过这个返回值我们可以找到这个定时器 在vue项目中可以使用路由 ...

  9. Java学习笔记十七:Java中static使用方法

    Java中static使用方法 一:Java中的static使用之静态变量: 我们都知道,我们可以基于一个类创建多个该类的对象,每个对象都拥有自己的成员,互相独立.然而在某些时候,我们更希望该类所有的 ...

  10. 安装使用supervisor来启动服务

    supervisor 使用方法 supervisor(官网)是一个unix的系统进程管理软件,可以用它来管理apache.nginx等服务, 若服务挂了可以让它们自动重启.当然也可以用来实现golan ...