volatile

1. 含义:

volatile是JVM提供的轻量级的同步机制,具有三个特点:保证可见性、不保证原子性、禁止指令重排。

1.1 保证可见性

一个线程修改了共享变量并写回主内存,其他线程可以自动知道共享变量发生了改变;即共享变量的变化对其他线程可见。这种自动不是指线程自身主动去读主内存的变量,而是由JVM主动告知各线程。通过volatile,解决了工作内存与主内存之间数据同步延迟造成的不可见问题。

验证:

    1. import java.util.concurrent.TimeUnit;
    2.  
    3. class MyData{
    4. volatile int number = 0; //number就是共享变量
    5. public void add(){
    6. this.number = 10;
    7. }
    8. }
    9. /*
    10. 1 验证volatile的可见性
    11. 1.1 加入int number=0,number变量之前根本没有添加volatile关键字修饰,没有可见性
    12. 1.2 添加了volatile,可以解决可见性问题
    13.  
    14. * */
    15. public class VolatileDemo_1 {
    16. public static void main(String[] args){
    17. MyData myData = new MyData();
    18. System.out.println("initial:"+myData.number);
    19.  
    20. new Thread(()->{
    21. System.out.println(Thread.currentThread().getName()+":start");
    22. try{ TimeUnit.SECONDS.sleep(3);} catch(InterruptedException e) {e.printStackTrace();}
    23. myData.add();
    24. System.out.println(Thread.currentThread().getName()+":"+myData.number);
    25. System.out.println(Thread.currentThread().getName()+":finish");
    26. },"A").start();
    27.  
    28. while(myData.number == 0){
    29. //System.out.println("loop");
    30. }
    31.  
    32. System.out.println(Thread.currentThread().getName()+":"+myData.number);
    33. }
    34. }

分析:

[ 1 ] 当没有添加volatile关键字,是不可见状态;

- while循环体内为空时:这时候,A是一个线程,经过3秒,A修改了变量number=10,并写回主内存;main是主线程,由于不可见,main无法自动获取number的变化,其值仍然为0,阻塞在while循环。如下图所示。

-  while循环体内不为空时(如一个输出语句):同样,A是一个线程,经过3秒,A修改了变量number=10,并写回主内存;main是主线程,会读取主内存里面的值并执行循环体;虽然由于不可见不能自动获取number的变化,但它可以主动去读取主内存中变量的值,当主内存变量值变成10,则主线程会退出循环体。

[ 2 ] 当添加volatile关键字,是可见状态;(即 volatile int number = 0;)

-  while循环体内为空时:这时候,A是一个线程,经过3秒,A修改了变量number=10,并写回主内存;main是主线程,由于number有volatile修饰,为可见状态,main可以自动获取number的变化,其值变为10,可以自动退出while循环。这就说明了a线程对变量的修改对其他线程可见。

1.2 不保证原子性

原子性指的是不可分割,完整一体;在程序中,指的是线程执行某个事务时不被分割,事务里的指令要么全部成功,要么全部失败。在并发环境中,如果不能保证原子性,则程序结果可能出错。(不保证原子性,则并发的线程之间互相干扰,影响各种线程的事务的完整执行)。volatile的机制不保证原子性,因此说是轻量级的同步机制。

代码示例:

    1. import java.util.concurrent.TimeUnit;
    2. import java.util.concurrent.atomic.AtomicInteger;
    3.  
    4. class MyData{
    5. volatile int number = 0; //number就是共享变量
    6. public void addPlusPlus(){
    7. number++; //++操作包括三步,从主内存读数据;加;写回主内存
    8. }
    9.  
    10. AtomicInteger atomicInteger = new AtomicInteger();
    11. public void addMyAtommic(){
    12. atomicInteger.getAndIncrement();
    13. }
    14. }
    15. /*
    16.  
    17. 验证volatile不保证原子性
    18.  
    19. 1 原子性是不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割。
    20. 需要整体完成,要么同时成功,要么同时失败。
    21.  
    22. 2 volatile不可以保证原子性演示
    23.  
    24. 3 如何解决原子性
    25. *加sync
    26. *使用我们的JUC下AtomicInteger
    27.  
    28. * */
    29. public class VolatileDemo {
    30. public static void main(String[] args){
    31. MyData myData = new MyData();
    32. for (int i = 1; i <= 10 ; i++) { //建立10个线程
    33. new Thread(()->{
    34. for (int j = 1; j <= 1000 ; j++) {
    35. myData.addPlusPlus();
    36. myData.addMyAtommic();
    37. }
    38. },String.valueOf(i)).start();
    39. }
    40.  
    41. //需要等待上述10个线程都计算完成后,再用main线程去的最终的结果是多少?
    42. while(Thread.activeCount() > 2){
    43. Thread.yield(); //当前面的线程没有执行完,则不执行后面的主线程
    44. }
    45. ////结果不为10000,说明不保证原子性;
    46. //因为原子性意味着最终一致性,我们的算法逻辑的结果是10000,如果不等于10000,说明线程并发执行有互相干扰,导致原子性被破坏。
    47. System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
    48. System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.atomicInteger);
    49. }
    50. }

    分析:可能出现两个线程在可见性还没生效时同时把加的结果写回主内存,造成+的操作出现冲突而丢失一些自增操作。

  • 结果:

1.3 禁止指令重排

在计算机执行程序时,为了提高性能,编译器和处理器会对指令进行重排。主要有编译器优化重排,指令并行重排、内存优化重排。指令重排时会考虑的是指令之间的依赖性(如:如果B依赖A,则B在A后)。如果是单线程,这些重排不会对结果造成影响;但如果是单线程,指令重排可能会造成程序执行出错。volatile通过禁止指令重排来保证有序性。有序性(以及可见性)是通过内存屏障(memory barrier)实现的。指令的有序性是保证最终数据一致性的基础。

拓展:

1) 内存屏障是一个CPU指令,是底层原语,其主要作用有两个:其一,是保证特定操作的有序性,通过插入内存屏障指令可以使内存屏障前后的指令不会重排序。其二,是保证某些变量的内存可见性,方法是强制cpu缓冲数据的刷出,从而使其他线程可以从内存到读取到特定变量的最新版。

2)单例模式(singleton):单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。单例模式常见的有懒汉方式(类加载的时候,不会创建对象,调用时才会创建对象。因此类加载速度快,线程相对不安全)和饿汉方式(类加载的时候,创建对象。 因此类加载速度慢, 线程相对安全)。懒汉模式线程不安全,可以通过synchronized对代码段进行加锁保证线程安全,但限制了高并发。

3)多线程下的单例DCL(双端检锁机制):通过两次判断,一次加锁,从而避免了整个代码段的加锁;但仍然线程不安全,因为有指令重排的存在。可以通过volatile修饰禁止指令重排,从而最终保证了线程安全。

【java】关键字volatile的更多相关文章

  1. JAVA关键字Volatile的特性

    一.简述: 关键字Volatile是JAVA虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确.完整的理解,以致于许多程序员在遇到需要处理多线程数据竞争的时候一律使用synchronized来进 ...

  2. Java 关键字volatile的解释

    volatile 关键字特征: 1.可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.可以禁止线程的工作内存对volatile修饰的变量进行缓存,并将修改的变量立即写入主存. 2. ...

  3. Java关键字-volatile

    关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制. 一旦某个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1.保证了不同线程对这个变 ...

  4. Java内存模型及Java关键字 volatile的作用和使用说明

    先来看看这个关键字是什么意思:volatile  [ˈvɒlətaɪl] adj. 易变的,不稳定的; 从翻译上来看,volatile表示这个关键字是极易发生改变的.volatile是java语言中, ...

  5. 关于 Java 关键字 volatile 的总结

    1 什么是 volatile volatile 是 Java 的一个关键字,它提供了一种轻量级的同步机制.相比于重量级锁 synchronized,volatile 更为轻量级,因为它不会引起线程上下 ...

  6. java 关键字volatile

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  7. java关键字volatile用法详解

    volatile关键字想必大家都不陌生,在java 5之前有着挺大的争议,在java 5之后才逐渐被大家接受,同时作为java的关键字之一,其作用自然是不可小觑的,要知道它是java.util.con ...

  8. 深入汇编指令理解Java关键字volatile

    volatile是什么 volatile关键字是Java提供的一种轻量级同步机制.它能够保证可见性和有序性,但是不能保证原子性 可见性 对于volatile的可见性,先看看这段代码的执行 flag默认 ...

  9. Java 关键字volatile 与 synchronized 作用与区别

     1,volatile 它所修饰的变量不保留拷贝,直接访问主内存中的.    在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器).为了性能,一个线程会在自己 ...

  10. Java关键字volatile的实现原理(四)

    简述 volatile 是轻量级的synchronized,在多线程开发中保证了共享变量的可见性.可见性就是当一个线程修改一个共享变量时,另一个线程可以读到修改的值.如果volatile变量使用恰当, ...

随机推荐

  1. Semaphores

    信号量和P,V原语的使用可归纳为三种情形: 把信号量视为加锁标志位,其目的是为了实现对某个唯一的共享数据的互斥访问,如各个进程间的某共享变量,数据库中的某个记录. 共享数据的值与信号量本身的值没有直接 ...

  2. 一只简单的网络爬虫(基于linux C/C++)————主事件流程

    该爬虫的主事件流程大致如下: 1.获取命令行参数,执行相应操作 2.读取配置文件,解析得到各种设置 3.载入各种模块 4.种子入队,开启DNS解析线程(原始队列不为空时解析) 5.创建epoll,开启 ...

  3. spark系列-8、Spark Streaming

    参考链接:http://spark.apache.org/docs/latest/streaming-programming-guide.html 一.Spark Streaming 介绍 Spark ...

  4. Blazor一个简单的示例让我们来起飞

    Blazor Blazor他是一个开源的Web框架,不,这不是重点,重点是它可以使c#开发在浏览器上运行Web应用程序.它其实也简化了SPA的开发过程. Blazor = Browser + Razo ...

  5. libevent(六)事件监听

    libevent是如何实现事件监听的呢? 在Linux,libevent的底层实现是epoll,因此实现事件监听的方式就是,把需要监听的fd加入epoll中. I/O事件 定时器事件 定时器事件没有f ...

  6. redis-py中的坑

    今天发现,使用redis-py从redis中获取的数据竟然是加密的. conn = redis.Redis(host='redis_serverip', port=6379, password='re ...

  7. Coursera课程笔记----计算导论与C语言基础----Week 8

    C语言中的运算成分(Week 8) 赋值运算符 "="赋值运算符 给赋值号左边的变量赋予数值 在变量定义的同时可以为变量赋初值 要点一:两面类型不同 若=两边的类型不一致,赋值时要 ...

  8. 【Kafka】CAP理论以及CAP定律

    目录 CAP理论 概述 Consistency Availability Partition Tolerance CAP理论 概述 1988年,加州大学计算机科学家Eric Brewer 提出了分布式 ...

  9. Linux下ffmpeg交叉编译

    1 获取源代码 git clone -b "branch" https://git.ffmpeg.org/ffmpeg.git "branch" 可以是以下的m ...

  10. .net core grpc单元测试 - 服务器端

    前言 gRPC凭借其严谨的接口定义.高效的传输效率.多样的调用方式等优点,在微服务开发方面占据了一席之地.dotnet core正式支持gRPC也有一段时间了,官方文档也对如何使用gRPC进行了比较详 ...