精华内容
下载资源
问答
  • volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。这篇文章主要介绍了Javavolatile关键字的作用与用法详解的相关资料,需要的朋友可以参考下
  • 主要介绍了Java Volatile关键字实现原理过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 在本文里我们给大家分享的是关于java volatile关键字作用及使用场景的相关知识点内容,需要的朋友们学习下。
  • 主要介绍了java volatile关键字使用方法及注意事项的相关资料,当一个变量被声明为 volatile 后,java 内存模型确保所有使用该变量的线程能看到相同的、一致的值。,需要的朋友可以参考下
  • java volatile 关键字详解

    千次阅读 热门讨论 2021-04-03 15:30:49
    一,什么是volatile关键字,作用是什么 ​ volatile是java虚拟机提供的轻量级同步机制 ​ 作用是: 1.保证可见性 2.禁止指令重排 3.不保证原子性 本篇具体就讲解 什么叫保证了可见性, 什么叫禁止指令重排,什么是原子性 ...

    java volatile 关键字详解

    一,什么是volatile关键字,作用是什么

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

    ​ 作用是: 1.保证可见性 2.禁止指令重排 3.不保证原子性

    本篇具体就讲解 什么叫保证了可见性, 什么叫禁止指令重排,什么是原子性

    而在这之前需要对JMM 有所了解

    二,什么是JMM

    ​ JMM(java 内存模型 Java Memory Model 简称JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式.

    JMM的同步规定:

    ​ 1.线程解锁之前,必须把共享变量刷新回主存

    ​ 2.线程加锁锁之前,必须读取主存的最新值到自己的工作空间

    ​ 3.加锁解锁必须是 同一把锁

    ​ 由于 JMM运行程序的实体是线程.而每个线程创建时JMM都会为其创建一个自己的工作内存(栈空间),工作内存是每个线程的私有 数据区域.而java内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程的变量的操作(读取赋值等)必须在自己的工作内存中去进行,首先要 将变量从主存拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量操作完后的新值写回主内存,不能直接操作主内存的变量,各个线程的工作内存中存储着主内存的变量拷贝的副本,因IC不同的线程间无法访问对方的工作内存,线程间的通信必须在主内存来完成, 其简要访问过程如下图:

    在这里插入图片描述

    三,可见性

    ​ 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    ​ 通过前面的 JMM介绍,我们知道各个线程对主内存的变量的操作都是各个线程各自拷贝到自己的工作内存中进行操作,然后在写回主内存中

    ​ 这就可能存在一个线程a修改了共享变量X的值但还未写回主内存,又有一个线程b对共享变量X进行操作,但 此时线程a的工作内存的共享变量X对线程吧来说是不可见的,这种工作内存与主内存同步延迟的问题就造成了可见性问题

    四,不保证原子性

    ​ 原子性:某个线程在执行某项业务时,中间不可被加塞或分割,需要整体完整。要么同时成功,要么同时失败

        class MyData{volatile int number = 0;
    ​    Object object = new Object();
    
        public void addTo60(){
            this.number = 60;
        }
        
        public void addPlusPlus(){
            this.number++;
        }
        
        AtomicInteger atomicInteger = new AtomicInteger();
        
        public void addAtomic(){
            atomicInteger.getAndIncrement();
        }
    }
    
    /**
     * 验证volatile的可见性
    
     * 1.当number未被volatile修饰时,new Thread将number值改为60,但main线程并不知道,会一直在循环中出不来
    
     * 2.当number使用volatile修饰,new Thread改变number值后,会通知main线程主内存的值已被修改,结束任务。体现了可见性
     *
    
     * 验证volatile不保证原子性
    
     * 1.原子性是指,某个线程在执行某项业务时,中间不可被加塞或分割,需要整体完整。要么同时成功,要么同时失败
     *
    
     * 如何解决呢?
    
     * 1.使用synchronize
    
     * 2.使用AtomicInteger
     *
     */
    public class VolatileDemo {
        public static void main(String[] args) {
            //seeByVolatile();
            atomic();
        }
        //验证原子性
        public static void atomic() {
            MyData myData = new MyData();
            for (int i = 1; i <= 20; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 1; j <= 1000; j++) {
                            /*synchronized (myData.object){
                                myData.addPlusPlus();
                            }*/
                            myData.addPlusPlus();
                            myData.addAtomic();
                        }
                    }
                }).start();
            }
    
            //等待上面20个线程全部计算结束
            while (Thread.activeCount() > 2){
                Thread.yield();
            }
            
            System.out.println(Thread.currentThread().getName() + "int finally number is " + myData.number);
            System.out.println(Thread.currentThread().getName() + "AtomicInteger finally number is " + myData.atomicInteger);
        }
    
        //验证可见性的方法
        public static void seeByVolatile() {
            MyData myData = new MyData();
            //第一个线程
            new Thread(){
                public void run(){
                    System.out.println(Thread.currentThread().getName() + " come in");
                    try {
                        sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    myData.addTo60();
                    System.out.println(Thread.currentThread().getName() + " update number to " + myData.number);
                }
            }.start();
    
            //第二个线程 main
            while (myData.number == 0){
            
            }
            System.out.println(Thread.currentThread().getName() + "mission is over");
        }
    }
    

    number++在多线程下是非线程安全,不是原子性操作?

    在这里插入图片描述

    五,禁止指令重排

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

    在这里插入图片描述

    单线程的环境里指令重排确保最终执行的结果和代码顺序执行的结果一致

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

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

    实例一:

    在这里插入图片描述

    实例二:

    在这里插入图片描述

    线程操作资源类,线程1访问method1,线程2访问method2,正常情况顺序执行,a=6
    多线程下假设出现了指令重排,语句2在语句1之前,当执行完flag=true后,另一个线程马上执行method2,a=5

    所以volatile 禁止指令重排,从而避免多线程的 环境下出现执行乱序 的情况

    六:使用volatile 的经典案例

     单例DCL的代码
    
    public class SingletonDemo {
        private static SingletonDemo instance = null;
    
        private SingletonDemo(){
            System.out.println(Thread.currentThread().getName() + "构造方法");
        }
        
        //DCL双端加锁机制
        public static SingletonDemo getInstance(){
            if (instance == null){
                synchronized (SingletonDemo.class){
                    if (instance == null){
                        instance = new SingletonDemo();
                    }
                }
            }
            return instance;
        }
    }
    

    这种写法在多线程条件下可能正确率为99.999999%,但可能由于指令重排出错

    原因在于某一个线程执行到第一次检测,读取到instance不为null,instance引用对象可能还没有完成初始化.

    instance = new SingletonDemo();; 分为一下三步

    1. memory = allocate() //分配内存
    2. ctorInstanc(memory) //初始化对象
    3. instance = memory //设置instance指向刚分配的地址

    2 ,3 步不存在数据依赖, 可以指令重排的执行顺序为 1 ,3 ,2,设置instance指向刚分配的地址,次数instance还没有初始化完

    但此时instance不为null了,若正好此时有一个线程来访问,就出现了线程安全问题

    所以需要添加volatile 关键字

    public class SingletonDemo {
        private static volatile SingletonDemo instance = null;
    
        private SingletonDemo(){
            System.out.println(Thread.currentThread().getName() + "构造方法");
        }
        //DCL双端加锁机制
        public static SingletonDemo getInstance(){
            if (instance == null){
                synchronized (SingletonDemo.class){
                    if (instance == null){
                        instance = new SingletonDemo();
                    }
                }
            }
            return instance;
        }
    }
    
    展开全文
  • 主要介绍了简单了解volatile关键字实现的原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Java语言包含两种内在的同步机制:同步块(或方法)和volatile变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile变量的同步性较差...

    目录

    一、简介

    二、并发编程的3个基本概念

    三、锁的互斥和可见性

    四、Java的内存模型JMM以及共享变量的可见性

    五、volatile变量的特性

    六、volatile不适用的场景

    七、volatile原理

    八、单例模式的双重锁为什么要加volatile


    一、简介

    volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

    二、并发编程的3个基本概念

    1.原子性

         定义即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行

        原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:

    (1)基本类型的读取和赋值操作,且赋值必须是值赋给变量,变量之间的相互赋值不是原子性操作。

    (2)所有引用reference的赋值操作

    (3)java.concurrent.Atomic.* 包中所有类的一切操作

    2.可见性

       定义指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

       在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

    3.有序性

       定义:即程序执行的顺序按照代码的先后顺序执行。

       Java内存模型中的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

       在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

    三、锁的互斥和可见性

       锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。

    (1)互斥即一次只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据。

    (2)可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。也即当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的如果没有同步机制提供的这种可见性保证,线程看到的共享变  量可能是修改前的值或不一致的值,这将引发许多严重问题。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

        a.对变量的写操作不依赖于当前值。

        b.该变量没有包含在具有其他变量的不变式中。

      实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。事实上就是保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

    四、Java的内存模型JMM以及共享变量的可见性

     JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

       对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,比较合理的方式其实就是volatile。

      需要注意的是,JMM是个抽象的内存模型,所以所谓的本地内存,主内存都是抽象概念,并不一定就真实的对应cpu缓存和物理内存

    五、volatile变量的特性

     1.保证可见性,不保证原子性

      (1)当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;

      (2)这个写会操作会导致其他线程中的volatile变量缓存无效。

     2.禁止指令重排

        重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:

     (1)重排序操作不会对存在数据依赖关系的操作进行重排序。

      比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。

     (2)重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变

      比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

        重排序在单线程下一定能保证结果的正确性,但是在多线程环境下,可能发生重排序,影响结果,下例中的1和2由于不存在数据依赖关系,则有可能会被重排序,先执行status=true再执行a=2。而此时线程B会顺利到达4处,而线程A中a=2这个操作还未被执行,所以b=a+1的结果也有可能依然等于2。

    public class TestVolatile{
    	int a = 1;
    	boolean status = false;
    	
    	//状态切换为true
    	public void changeStatus{
    		a = 2;   //1
    		status = true;  //2
    	}
    	
    	//若状态为true,则为running
    	public void run(){
    		if(status){   //3
    			int b = a + 1;  //4
    			System.out.println(b);
    		}
    	}
    
    }

         使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:

         a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

         b.在进行指令优化时,不能将对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

         即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。

    六、volatile不适用的场景

     1.volatile不适合复合操作

      例如,inc++不是一个原子性操作,可以由读取、加、赋值3步组成,所以结果并不能达到30000。.

      2.解决方法

     (1)采用synchronized

     (2)采用Lock

     (3)采用java并发包中的原子操作类,原子操作类是通过CAS循环的方式来保证其原子性的

    七、volatile原理

      volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

    (1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

    (2)它会强制将对缓存的修改操作立即写入主存;

    (3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

    八、单例模式的双重锁为什么要加volatile

    public class TestInstance{
    	private volatile static TestInstance instance;
    	
    	public static TestInstance getInstance(){        //1
    		if(instance == null){                        //2
    			synchronized(TestInstance.class){        //3
    				if(instance == null){                //4
    					instance = new TestInstance();   //5
    				}
    			}
    		}
    		return instance;                             //6
    	}
    }

     

       需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码

    a. memory = allocate() //分配内存
    
    b. ctorInstanc(memory) //初始化对象
    
    c. instance = memory //设置instance指向刚分配的地址

       上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。

    展开全文
  • 主要介绍了Java Volatile关键字同步机制详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • java volatile关键字作用及使用场景 1. volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。如以下代码片段,...

    1. volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。如以下代码片段,isShutDown被置为true后,doWork方法仍有执行。如用volatile修饰isShutDown变量,可避免此问题。

    复制代码

     1 public class VolatileTest3 {
     2     static class Work {
     3         boolean isShutDown = false;
     4 
     5         void shutdown() {
     6             isShutDown = true;
     7             System.out.println("shutdown!");
     8         }
     9 
    10         void doWork() {
    11             while (!isShutDown) {
    12                 System.out.println("doWork");
    13             }
    14         }
    15     }
    16 
    17     public static void main(String[] args) {
    18         Work work = new Work();
    19 
    20         new Thread(work::doWork).start();
    21         new Thread(work::doWork).start();
    22         new Thread(work::doWork).start();
    23         new Thread(work::shutdown).start();
    24         new Thread(work::doWork).start();
    25         new Thread(work::doWork).start();
    26         new Thread(work::doWork).start();
    27     }
    28 }

    复制代码

    出现脏读时,运行结果如下:

    2. 为什么会出现脏读?

    Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。变量的值何时从线程的工作内存写回主存,无法确定。

    3. happens-before规则的理解与勘误

    在网上查volatile关键字相关信息时,多篇博客提到了happens-before原则,个人对此原则的理解是:当操作该volatile变量时,所有前序对该变量的操作都已完成(如不存在已变更,但未写回主存的情况),所有后续对该变量的操作,都未开始。仅此而已。

    这里,我认为网上很常见的一个理论对此理解有误,如下图。此观点认为,由于volatile变量flag的happens-before原则,所以A线程2处对其的写操作一定先于B线程3处对其的读操作。其实这种观点是有逻辑缺陷的,如果存在一个C线程,先读取flag的值,后写入flag的值,那C线程的执行时机是什么呢?如果还有其他D、E线程呢。。。对于这段代码的正确理解是,只要3处拿到的flag是true,那么a的值一定是1,而不是0.因为volition修饰的变量,处理器不会对其进行重排序,所以1处对a的赋值,一定发生在2处对flag的赋值之前。如果flag不是volatile变量,那么1处和2处代码的执行顺序是无法保证的(处理器的指令重排序),虽然大部分情况1会先于2执行。happens-before原则约束的并不是多线程对同一变量的读和写操作之间的顺序,而是保证读操作时,前序所有对该变量的写操作已生效(写回主存)。

     

     

    验证如下:

    复制代码

     1 public class VolatileTest {
     2     static class A {
     3         int a = 0;
     4         volatile boolean flag = false;
     5 
     6         void writer() {
     7             a = 1;                   //1
     8             flag = true;               //2
     9             System.out.println("write");
    10         }
    11 
    12         void reader() {
    13             if (flag) {                //3
    14                 int i =  a;           //4
    15                 System.out.println("read true");
    16                 System.out.println("i is :" + i);
    17             } else {
    18                 int i = a;
    19                 System.out.println("read false");
    20                 System.out.println("i is :" + i);
    21             }
    22         }
    23 
    24     }
    25 
    26     public static void main(String[] args) {
    27         A aaa = new A();
    28         new Thread(() -> aaa.reader()).start();
    29         new Thread(() -> aaa.writer()).start();
    30     }
    31 }

    复制代码

    运行结果如下,在写操作执行之前,读操作已完成

     

     4. volatile关键字使用场景

    注意:volatile只能保证变量的可见性,不能保证对volatile变量操作的原子性,见如下代码:

     

    复制代码

     1 public class VolatileTest2 {
     2     static class A {
     3         volatile int a = 0;
     4         void increase() {
     5             a++;
     6         }
     7         int getA(){
     8             return a;
     9         }
    10     }
    11 
    12     public static void main(String[] args) {
    13         A a = new A();
    14 
    15         new Thread(() -> {
    16             for (int i = 0;i < 1000;i++) {
    17                 a.increase();
    18             }
    19             System.out.println(a.getA());
    20         }).start();
    21         new Thread(() -> {
    22             for (int i = 0;i < 2000;i++) {
    23                 a.increase();
    24             }
    25             System.out.println(a.getA());
    26         }).start();
    27         new Thread(() -> {
    28             for (int i = 0;i < 3000;i++) {
    29                 a.increase();
    30             }
    31             System.out.println(a.getA());
    32         }).start();
    33         new Thread(() -> {
    34             for (int i = 0;i < 4000;i++) {
    35                 a.increase();
    36             }
    37             System.out.println(a.getA());
    38         }).start();
    39         new Thread(() -> {
    40             for (int i = 0;i < 5000;i++) {
    41                 a.increase();
    42             }
    43             System.out.println(a.getA());
    44         }).start();
    45     }
    46 }

    复制代码

     

    运行结果如下,volatile无法保证a++操作的原子性。

    展开全文
  • Java volatile关键字作用

    千次阅读 2018-11-02 09:59:53
    当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存“, 这里的”保证“ 是如何做到的?和 JIT的具体编译后的CPU指令相关吧?  volatile特性  内存可见性:通俗来说就是... 通过关键字sychronize...

    当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存“, 这里的”保证“ 是如何做到的?和 JIT的具体编译后的CPU指令相关吧?

      volatile特性

      内存可见性:通俗来说就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的。

      volatile的使用场景

      通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:

      1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;

      2、该变量没有包含在具有其它变量的不变式中,这句话有点拗口,看代码比较直观。

    public class NumberRange {
      private volatile int lower = 0;
    
      private volatile int upper = 10;
    
      public int getLower() { return lower; }
    
      public int getUpper() { return upper; }
    
      public void setLower(int value) {
    
         if (value> upper)
    
         throw new IllegalArgumentException(...);
    
         lower = value;
    
      }
    
      public void setUpper(int value) {
    
         if (value < lower)
    
         throw new IllegalArgumentException(...);
    
         upper = value;
    
       }
    }

    上述代码中,上下界初始化分别为0和10,假设线程A和B在某一时刻同时执行了setLower(8)和setUpper(5),且都通过了不变式的检查,设置了一个无效范围(8, 5),所以在这种场景下,需要通过sychronize保证方法setLower和setUpper在每一时刻只有一个线程能够执行。

    上述如果没有了解volatile的作用,那么看下下面的例子可以看出volatile在实际中的作用

    下面是我们在项目中经常会用到volatile关键字的两个场景:

      1、状态标记量

      在高并发的场景中,通过一个boolean类型的变量isopen,控制代码是否走促销逻辑,该如何实现?

    public class ServerHandler {
    
          private volatile isopen;
    
          public void run() {
             
            if (isopen) {
    
                //促销逻辑
                  
            } else {
    
               //正常逻辑
                  
            }
              
        }
    
        public void setIsopen(boolean isopen) {
    
           this.isopen = isopen
              
        }      
    }

    上述一个简单的案例我们可以清楚的看到,现实场景中用户执行了多线程中run()方法,如果需要开启促销逻辑,那么只需要后台设置调用setIsopen(true) 方法,就能很好的控制多线程中方法控制的问题了,该放说明volatile关键字的作用就是告诉该执行方法时时获取最新变量值。

     如何保证内存可见性?

      在java虚拟机的内存模型中,有主内存和工作内存的概念每个线程对应一个工作内存,并共享主内存的数据,下面看看操作普通变量和volatile变量有什么不同:

      1、对于普通变量:读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值。

      2、对于volatile变量,读操作时JMM会把工作内存中对应的值设为无效,要求线程从主内存中读取数据;写操作时JMM会把工作内存中对应的数据刷新到主内存中,这种情况下,其它线程就可以读取变量的最新值。

     

    volatile变量的内存可见性是基于内存屏障(Memory Barrier)实现的,什么是内存屏障?内存屏障,又称内存栅栏,是一个CPU指令。在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM为了保证在不同的编译器和CPU上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。

     

    class Singleton {
    
          private volatile static Singleton instance;
    
          private int a;
    
          private int b;
    
          private int b;
    
          public static Singleton getInstance() {
              
            if (instance == null) {
    
                  syschronized(Singleton.class) {
                      
                    if (instance == null) {
    
                          a = 1; // 1
                          b = 2; // 2
                          instance = new Singleton(); // 3
                          c = a + b; // 4
                    }
                      
                }
                  
            }
     
            return instance;  
        } 
    }

    1、如果变量instance没有volatile修饰,语句1、2、3可以随意的进行重排序执行,即指令执行过程可能是3214或1324。

    2、如果是volatile修饰的变量instance,会在语句3的前后各插入一个内存屏障。

    展开全文
  • 主要介绍了java volatile关键字的含义详解的相关资料,需要的朋友可以参考下
  • 我理解Java中的volatile关键字可以对引用变量和除long和double之外的所有原语进行读/写操作.我也知道复合语句,如递增整数,var,不是原子的,不应该用来代替synchronized语句.但是这堂课怎么样?public class Setter{...
  • Java volatile关键字的作用volatile关键字的作用使变量在多个线程之间可见。package com.wkcto.volatilekw;/*** volatile的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取* Author: 老崔*/public...
  • volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果,本文给大家介绍javavolatile关键字,需要的朋友参考下
  • 详细介绍了Java中的volatile关键字的概念、特性,随后从代码、字节码、JVM、底层CPU4个层面,深入系统底层的分析了volatile关键字的实现,最后介绍了volatile关键字的使用!
  • Java volatile关键字实例

    千次阅读 2017-07-04 21:43:18
    volatile关键字能保证可见性和有序性,但是不保证原子性。因此并不能保证线程安全。 看一个相关的例子:双重校验锁实现的单例模式:public class DoubleCheckSymbol { private static volatile DoubleCheckSymbol...
  • volatile关键字的意思是不稳定的,可变的 使用volatile关键字和不使用的区别在于JVM内存主存和线程工作内存的同步之上,volatile保证变量在线程工作内存和主存之间一致,其实就是告诉处理器,不要将我放进工作内存,...
  • java volatile 关键字

    2021-02-25 19:46:42
    volatile 关键字能把 Java 变量标记成"被存储到主存中"。这表示每一次读取 volatile 变量都会访问计算机主存,而不是 CPU 缓存。每一次对 volatile 变量的写操作不仅会写到 CPU 缓存,还会刷新到主存中。实际上从 ...
  • Java volatile关键字

    2018-01-15 12:34:33
    使用Java编写线程安全的程序关键在于正确的使用共享对象,以及安全的对其进行访问管理。Java的内置锁可以保障线程安全,对于其他的应用...Java对可见性提供的原生支持是volatile关键字volatile关键字  volati
  • java volatile关键字详解

    千次阅读 2018-03-29 16:17:45
    对于可见性,Java提供了volatile关键字来保证可见性。 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。 而普通的共享变量不能保证可见性...
  • public class T04_volatile { /*volatile*/ boolean flag = true; synchronized void m() { System.out.println("m start"); while (flag) { } System.out.println("m end"); } public st
  • Java 5之后,volatile关键字才得以重获生机。 Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是...
  • volatile 关键字通常使用在一次写,多次读的场景,使线程之间可以实时拿到更新的结果,但多次写却不能保证实时拿到 package test; public class ThreadJoinTest { private static class Count extends Thread{ ...
  • 提到volatile之前,我们首先来看看java的内存模型,因为他们之间有密切的关系。 计算机在执行的过程中,每条指令都有cpu来完成运算,势必要涉及读取数据和写入数据,由于程序执行过程中,数据是存放在主内存中,...
  • Java中的volatile关键字的功能volatile是java中的一个类型修饰符。它是被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器 失去大量优化...
  • java volatile关键字

    2017-08-26 17:27:32
    java volatile关键字因为在很多代码里总是看到这个关键字,但又没有具体的了解下,所以在这里梳理一下。主要讲解了线程之间的可见性和原子性问题。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 93,077
精华内容 37,230
关键字:

javavolatile关键字

java 订阅
友情链接: Thinning_Algorithm.rar