volatile学习

第一、java内存模型

共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,

每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

JMM关于同步的规定:

1 线程解锁前,必须把共享变量的值刷新回主内存

2线程加锁前,必须读取主内存的最新值到自己的工作内存

3加锁解锁是同一把锁

由于JVM运行程序的实体就是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方为称为栈空间),工作内存是每个线程的私有数据区域,

而Java内存模型总规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,

首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再讲变量写回主内存,不能直接操作主内存中的变量,

各个线程中的工作内存存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,

这个就是可见性

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

下面通过示意图来说明这两个步骤:

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。

当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。

从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

总结:什么是Java内存模型:java内存模型简称jmm义了一个线程另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,多个线程同时访问一个数据的时候,

可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

第二、Vilatile的特性

2.1.vilatile定义

volatile是java虚拟机提供的轻量级的同步机制

2.2.可见性

可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。

代码案例:

单线程情况下:

public class VolatileDemo03 {

    public static VolatileDemo03 instance = null;
    private VolatileDemo03(){
    }

    public static VolatileDemo03 getInstance(){
        if (instance==null){
            instance= new VolatileDemo03();
        }
        return instance;
    }
    public static void main(String[] args) {
        //在单线程情况下都是true
        System.out.println(VolatileDemo03.getInstance()==VolatileDemo03.getInstance());
        System.out.println(VolatileDemo03.getInstance()==VolatileDemo03.getInstance());
        System.out.println(VolatileDemo03.getInstance()==VolatileDemo03.getInstance());
    }
}

多线程情况:

public class VolatileDemo03 {

    public static volatile VolatileDemo03 instance = null;
    private VolatileDemo03(){
        System.out.println(Thread.currentThread().getName()+"构造函数");
    }

    //DCL(Double Check Lock双端检测机制)Volatile 保证了线程间共享变量的及时可见性
    public static VolatileDemo03 getInstance(){
        if (instance==null){
            synchronized(VolatileDemo03.class){
                if (instance==null){
                    instance= new VolatileDemo03();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) {
        //1.在不做任何处理的情况下构造方法打印了五次
        //2.使用volatile 之后构造函数只打印一次
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    VolatileDemo03.getInstance();
                }
            }).start();
        }
    }
}

2.3.不保证原子性

验证可见性和不保证原子性

不保证原子性的解决方法:

1.加sync

2.直接使用JUC下AtomicInteger

class MyData{
   volatile int number = 0;
    public void addNumber(){
        this.number = 60;
    }
    public void addAtomicity(){
        this.number++;
    }
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addMyAtomic(){
        atomicInteger.getAndIncrement();
    }
}

/**
 * 1验证volatile的可见性
 *    1.1假如int number=0;number变量之前根本没有添加volatile关键字修饰,没有可见性
 *    1.2添加了volatile,可以解决可见性问题
 * 2验证volatitle不保证原子性
 *      原子性:不可分割,完整性,也即某个线程正在做某个业务时,中间不可以被加载或者被分割。
 *      需要整体完整要么同时成功,要么同时失败
 *
 */
public class VolatileDemo01 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 0; i < 20; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 10000; j++) {
                            myData.addAtomicity();
                            myData.addMyAtomic();
                        }
                    }
                }).start();
        }
        //需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果看是多少?
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"最终结果值:"+myData.number);
        System.out.println(Thread.currentThread().getName()+"最终结果值:"+myData.atomicInteger);
    }

    //volatile可见保证可见性,及时通知其他线程,主物理内存的值以及被修改了
    public  static void seeVolatile(){
        MyData myData = new MyData();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("修改之前的值:"+myData.number);
                //等待三秒
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //调用改变的方法
                myData.addNumber();
                System.out.println("修改之后的值:"+myData.number);
            }
        }).start();

        while(myData.number==0){
            //一直循环等待
        }
        System.out.println(Thread.currentThread().getName()+":"+myData.number);
    }
}

2.4.禁止指令重排

volatile 禁止指令重排

JMM 有序性

计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令重排,一般分为以下3种

源代码→编译器优化的重排→内存系统的重排→最终执行的指令

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。

处理器在进行重排序时必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保住一致性是无法确定的,结果无法预测

禁止指令重排总结

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象

先了解一下概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:

一是保证特定操作的执行顺序,

二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和折腾Memory Barrier指令重排序,

也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

JMM线程安全得到保证

工作内存和主内存同步延时现象导致的可见性问题,可以使用synchronize或者volatile关键字进行解决,他们都可以使一个线程修改后的变量立即对其他线程可见。

对于指令重排导致的可见性问题和有序性问题

可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化

那些地方用过volatile

1.单例模式DCL代码  2单例模式volatile分析

public class VolatileDemo02 {

    private static volatile VolatileDemo02 instance = null;
    private VolatileDemo02(){
        System.out.println(Thread.currentThread().getName()+"构造函数");
    }

    //DCL(Double Check Lock双端检测机制)
    public static  VolatileDemo02 getInstance(){
        if (instance == null) {
            synchronized (VolatileDemo02.class){
                if (instance == null) {
                    instance = new VolatileDemo02();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
//        System.out.println(VolatileDemo02.getInstance()==VolatileDemo02.getInstance());
//        System.out.println(VolatileDemo02.getInstance()==VolatileDemo02.getInstance());
//        System.out.println(VolatileDemo02.getInstance()==VolatileDemo02.getInstance());
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    VolatileDemo02.getInstance();
                }
            }).start();
        }
    }
}

DCL(Double Check Lock双端检测机制)

DCL(双端检索)机制不一定线程安全,原因四有指令重排序的存在,加入volatile可以禁止指令重排

原因在与某一个执行到第一次检测,读取到的instance不为null时间,instance的引用对象可能没有完成初始化。

instance = new SingletonDemo();可以分为以下三步完成(伪代码)

memory = allocate();//1.分配对象内存空间

instance(memory );//2初始化对象

instance = memory //3设置instance执行分配的内存地址,此时instance!=null

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中没有变化,因此这种重排优化是允许的

memory = allocate();//1.分配对象内存空间

instance = memory //3设置instance执行分配的内存地址,此时instance!=null但是对象还没有初始化

instance(memory );//2初始化对象

但是指令重排只会保证串行语义的执行的一致性(单线程),但不会关系多线程间的语义一致性。

所以当一条数据线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

2.5Volatile与Synchronized区别

(1)从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。

(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。

synchronized太重了

原文地址:https://www.cnblogs.com/cxyyh/p/11173861.html

时间: 2024-10-18 20:48:45

volatile学习的相关文章

java 语言多线程可见性(synchronized 和 volatile 学习)

共享变量可见性实现的原理 java 语言层面支持的可见性实现方式: synchronized volatile 1. synchronized 的两条规定: 1 线程解锁前,必须把共享变量的最新值刷新到主内存中. 2 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁需要是同一锁) 线程解锁前对共享变量的修改在下次加锁时对其他线程可见. 2. volatile 实现可见性 深入来说,通过加入内存屏障和禁止重排序优化来实现 的. 对volatil

volatile 学习笔记

全面理解Java内存模型(JMM)及volatile关键字 正确使用 Volatile 变量 Java内存模型 在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步.通信是指线程之间以何种机制来交换信息.同步是指程序中用于控制不同线程间操作发生相对顺序的机制. 线程间的通信机制有两种:共享内存和消息传递.在共享内存的并发模型中,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信.在消息传递的并发模型中,线程之间没有公共状态,线程之间必须通过发消息来显示进行通信.

JAVA多线程基础学习三:volatile关键字

Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个关键字的作用就是告诉编译器,只要是被此关键字修饰的变量都是易变的.不稳定的.那为什么是易变的呢?因为volatile所修饰的变量是直接存在于主内存中的,线程对变量的操作也是直接反映在主内存中,所以说其是易变的. 一.Java内存模型 Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理

【java并发编程艺术学习】(三)第二章 java并发机制的底层实现原理 学习记录(一) volatile

章节介绍 这一章节主要学习java并发机制的底层实现原理.主要学习volatile.synchronized和原子操作的实现原理.Java中的大部分容器和框架都依赖于此. Java代码 ==经过编译==>Java字节码 ==通过类加载器==>JVM(jvm执行字节码)==转化为汇编指令==>CPU上执行. Java中使用的并发机制依赖于JVM的实现和CPU的指令. volatile初探 volatile是是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性.可见性

java学习:JMM(java memory model)、volatile、synchronized、AtomicXXX理解

一.JMM(java memory model)内存模型 从网上淘来二张图: 上面这张图说的是,在多核CPU的系统中,每个核CPU自带高速缓存,然后计算机主板上也有一块内存-称为主内(即:内存条).工作时,CPU的高速缓存中的数据通过一系列手段来保证与主内的数据一致(CacheCoherence),更直白点,高速缓存要从主内中load数据,处理完以后,还要save回主存. 上图说的是,java中的各种变量(variable)保存在主存中,然后每个线程自己也有自己的工作内存区(working me

C/C++学习之路----volatile

因为经常看见volatile这个关键词,想想自己对这个volatile也不是很清楚,仅仅知道它表明变量是易于变化的和防止编译器优化.所以就在网上找了一些其他道友对于volatile的理解,仔仔细细看了其他道友的见解,自己也大致明白这个关键词的作用.顺便把这些见解收集了下来,待以后忘记了在看一下: 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了.精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器

Java再学习——synchronized与volatile

volatile:只保证共享资源的可见性的,任何修改都写在主存,所有线程马上就能看到,适用于新值不依赖于旧值的情形. synchronized:保证可操作的原子性一致性和可见性. volatile和synchronized的区别: volatile仅仅使变量在值发生改变时能尽快地让其他线程知道,并不能保证多线程的执行有序性.而synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住. volatile仅能实现变量的修改可见性但不具备原子特性,而synchronize

C语言学习笔记(2):volatile与register类型修饰符

1.volatile volatile是易变的,不稳定的意思,volatile是关键字,是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统.硬件或者其他线程等,遇到这个关键字声明的变量,编译器对访问该变量的代码不在进行优化,从而可以提供对特殊地址的稳定访问.那么什么是编译器优化呢? 为了提高运行效率,攻城湿们可是费尽心机地把代码优化,把程序运行时存取速度优化.一般,分为硬件优化和软件优化.硬件优化,流水线工作,详细可以参考<计算机组成原理>.软件优化,一部分是程序

volatile关键字 学习记录1

虽然已经工作了半年了...虽然一直是在做web开发....但是平时一直很少使用多线程..... 然后最近一直在看相关知识..所以就有了这篇文章 用例子来说明问题吧 1 public class VolatileTest { 2 boolean b = false; 3 int a = 0; 4 5 public static void main(String[] args) { 6 for (int i=0; i<1000000; i++) { 7 VolatileTest resource =