volatile可以看成是轻量级的低配版的Synchronized,他主要是作用于共享变量,保证共享变量的可见性。确保共享变量在主内存中一致地准确的更新通知到各个线程,这是Volatile的可见性,同时由于它是低配版的Synchronized,所以他也没有了Synchronized的一些功能,比如原子性。

Java内存模型

在理解有关Java并发编程的时候,我们是非常有必要的先了解一下Java内存模型的。

Java内存模型规定了所有的变量都存储在主内存中,线程之间的工作并不是直接去读取操作主内存的,而是每个工作线程首先会在主内存中拷贝这些共享变量下来线程对变量的操作都是自己的工作线程中完成的,对于不同线程之间是具有不具备可见性的(各做各的),线程间变量值的传递均需要通过主内存来完成。Volatile正是为了解决以上的问题而存在的。

工作线程操作变量与主内存的交互

  • read:从主内存中读取变量
  • load:复制变量到工作线程本地内存作为副本
  • use:运算副本
  • assign:给副本赋值
  • store:副本写入工作线程的内存中
  • write:线程内存中的共享副本刷新主内存中的共享变量

可见性

可见性是指线程之间变量的可见性,及时得到变量状态变化的通知,一个线程修改了变量的状态另一个就及时的知道变量的最新状态。

举个例子:A、B线程在主内存中拷贝同一个Volatile修饰变量,A线程把这个变量的状态由false改为了true,紧跟着B线程就会收到通知他刚刚拷贝的变量已经过期失效,B线程就会更新这个变量,得到最新的状态true,而不再是过期失效的状态。

Volatile修饰的变量不允许线程内部缓存和重排序,也就是说直接操作主内存,这样就对其他线程可见了。但是,但是,但是,Volatile修饰的变量只能保证变量的可见性,而不能保证变量的原子性,这样就导致一些非原子性的操作仍然存在线程安全的问题。

普通的共享变量在进行操作之后,写入主内存的时机是不确定的,该线程可能在操作完变量并且还没写入主内存的时候就去干别的事情了,这样就导致在其他线程获取这个变量的时候并不是最新的值,无法保证可见性,真是这样的线程安全问题也就导致了程序运行结果并不是我们所期望的结果。

Volatile并不能保证原子性,而他的高配版——Synchronized完全应付了这些问题。Synchronized既能保证可见性又能保证原子性。Synchronized在工作时只能有一个线程获取锁执行同步代码,并在释放锁的时候把变量写入主内存中。

public class MyThread extends Thread {public boolean exit = false; public void run() { while (!exit){//do something}}
}复制代码

上面的代码使用退出标志终止线程关闭的代码。看上去似乎是没有问题,只要其他线程吧exit复制为true就能够终止线程。但是这样写仍然会存在风险,有可能不是我们所期望的效果。上面说过,工作线程会各自在主线程中拷贝变量,然后自顾自的工作。

以实例来说,A、B线程会在主线程中各自拷贝exit变量到自己的工作内存中,当B需要终止A线程的时候,B便会修改自己工作内存中的exit变量,但是由于不确定性B在修改本地exit变量的时候可能还没把修改后的变量exit写入主内存中就去了干别的事情了,导致A线程没有终止。

给exit变量增加Volatile修饰后,B线程把本地变量exit赋值为true的时候,Volatile会强制把最新值写到主内存中并且会通知A线程告知其本地exit变量已过期失效,立即到主内存中更新exit变量,这样子便会使A线程的exit变量及时更新。也体现了Volatile在线程之间的可见性。

原子性

从Volatile可见性的问题中我们带出了原子性这一名词。原子性是指:一个操作或者多个操作(可以把它看成事务)要么全执行而且不会被中断,要么全不执行。在Java中,对基本数据类型的变量的读取和赋值操作是就原子性操作。原子就是不能再细分的意思。

举个例子:int a = 8; 把8赋值给a这个操作已经不能再细分或分割了,那么类似于这种操作就称之为原子操作。

再举个例子:i++; 这个操作就可以分解为 i = i + 1 ,那么类似于这从操作就称之为非原子操作

非原子操作带来的是线程安全问题,使用同步技术Synchronized来把这堆操作变成一个原子操作。

public class Test {    public volatile int inc = 0; public void increase() {inc++;    }     public static void main(String[] args) {    final Test test = new Test();    for(int i=0;i<10;i++){            new Thread(){@Override                public void run() {                    for(int j=0;j<1000;j++)                        test.increase();                };            }.start();        }         while(Thread.activeCount()>1){System.out.println(test.inc);   }  //保证前面的线程都执行完 Thread.yield();         }
}复制代码

上面代码,我们直观的认为新建了10个线程,每个线程都对inc变量自增1,那么10个线程最后输出的结果自然是1000*10=10000,这是我们所期望的。但是通过输出我们发现结果并不是我们所想要的。

前面提到过,Volatile只能保证变量的可见性,而不能保证原子性。

还是按实例来说,A、B线程创建后各自把inc拷贝到自己的本地内存中。此时A、B都在自己本地线程中对inc++自增,然后A、B线程把运算后的结果写入主内存中。这样,尽管A、B线程都进行了自增的运算,我们的期望是等于3,尽管进行了两次自增,但是此时主内存中的inc变量只是2。

例子也体现了Volatile并不能保证非原子操作,仍然会存在线程安全问题。

解决方案:可以通过synchronized或lock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger。(不在本文范围)

有序性

有序性就是程序执行的顺序按照代码的先后顺序执行。Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。处理器的重排序不会影响单个线程,但是面临并发编程的时候就不能保证正确性了。

volatile关键字可以保证一定的“有序性”。synchronized既保证有序性同样保证原子性。

Volatile原理

在对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。 这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。

为了保证各个处理器的工作线程一致,每个处理会通过嗅探在总线上传播的数据来检查自己的缓存是否过期,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。

小结

synchronized​是防止多个线程同时执行一段代码,这样同步就会影响程序执行效率,而volatile在某些情况下性能要优于synchronized。Volatile只能保证变量的可见性,并不能保证变量的原子性。对于由于非原子操作而产生的线程安全问题,还是请使用synchronized。

最后使用Volatile的必备两个条件:

  • 对变量的操作不依赖于当前值,也就是原子操作
  • 该变量没有包含在具有其他变量的不变式中

That's all Thank you~

----- End -----

更多好文

请扫描下面二维码

欢迎关注~

Java并发编程——volatile相关推荐

  1. null在java存在的意义何在,Java并发编程——volatile关键字

    一.volatile是什么 volatile是Java并发编程中重要的一个关键字,被比喻为"轻量级的synchronized",与synchronized不同的是,volatile ...

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

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

  3. Java并发编程—volatile关键字(保证变量的可见性、有序性机制)

    原文作者:Matrix海子 原文地址:Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程 ...

  4. java并发编程-volatile内存实现和原理

    2019独角兽企业重金招聘Python工程师标准>>> 前面的博文说了java的内存模型,介绍了java内存模型的基础,此篇文章来说一下volatile关键字,这个在并发编程中,占有 ...

  5. Java并发编程-Volatile和Syncronized关键字

    Java并发编程学习分享的目标 了解Java并发编程中常用的工具和类用途与用法 了解Java并发编程工具的实现原理与设计思路 了解并发编程中遇到的常见问题与解决方案 了解如何根据实际情景选择更合适的工 ...

  6. Java并发编程:volatile的使用

    1.CPU.主存及高速缓存的概念 计算机的硬件组成可以抽象为由总线.IO设备.主存.处理器(CPU)等组成.其中数据存放在主存中,CPU负责指令的执行,CPU的指令执行非常快,大部分简单指令的执行只需 ...

  7. Java并发编程--volatile关键字解析

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

  8. Java并发编程-volatile

    一. volatite 简述 Java 语言提供了一种稍弱的同步机制,即 volatile 变量.用来确保将变量的更新操作通知到其他线程,保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新. ...

  9. Java并发编程-volatile关键字介绍

    前言 要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么 ...

最新文章

  1. centos 安装 erlang
  2. python练习题实例_Python 练习实例65
  3. cgi备份还原和ghost有什么区别_装系统教程!如何用ghost安装系统(下)!小白也能变装机大神!...
  4. 另一个域的cookie_一定要知道的第一方Cookie和第三方Cookie
  5. python某公司为员工发放奖品_python 练习2
  6. LeetCode 4. 寻找两个有序数组的中位数(二分查找,难)
  7. vue ui框架_Vue移动端UI框架指南
  8. Vue.js 2.0 参考手册.CHM下载
  9. R语言的特征选择(Feature Selection)包:Boruta和caret
  10. bgp 建立邻居发送的报文_HCIE笔记-------BGP邻居状态详解
  11. asymptotic notation and recursion
  12. 单网口RFC2544测试——信而泰网络测试仪实操
  13. 免费的上网行为管理系统和软路由系统推荐。
  14. 开发APP需要准备哪些工作
  15. ccleaner专业版注册码
  16. Input Leakage Current
  17. 超详细Netty入门
  18. 数字化时代,企业应该如何看待商业智能BI
  19. 【Qt】 Fractal Designer 5.3 Bug Report
  20. 如何在eLance,oDesk或Guru.com等外包平台上赢得任何项目?

热门文章

  1. 复习笔记(一)——C++基础
  2. MySQL 中 MyISAM 中的查询为什么比 InnoDB 快?
  3. cd返回上一 git_git统计代码量脚本
  4. java ear war_[JAVA语法]怎样制作ear,war文件
  5. 的远程烧写_农用气象环境远程监测管理系统
  6. java绘图板_Java中的画图板简单功能实现
  7. c语言中div函数,C 库函数
  8. 【新星计划】汽车纵向动力学模型
  9. 2021-05-08 docker  拷贝东西到镜像,和拷贝到宿主机
  10. LeetCode 198 House Robber Python