zoukankan      html  css  js  c++  java
  • Java关键字-volatile

      关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制。

      

      一旦某个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

      1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

      Java 内存模型规定了所有的变量都存储在主内存(Main Memory)中(仅是虚拟机内存的一部分)。每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

      某线程在对自己工作内存中的某变量进行写操作时,如果发现该变量是一个使用volatile修饰的变量,那么它会将该变量的值更新到主内存中,此举会导致其他线程各自工作内存中保存的副本变量失效。而其他线程在读取各自工作内存中的副本变量时,发现副本变量已失效,就会转向去主内存中读取变量。这就使得所有线程读取到的该变量的值永远是最新的,保证了该变量的可见性。 

      2.禁止进行指令重排序,从而保证了指令执行的有序性。

      该点语义,最常见的使用场景便是单例模式。

    package com.huang.pims.demo.patterns.singleton;
    
    public class VolatileSingleton {
    
        private static volatile VolatileSingleton singletonInstance;
    
        private VolatileSingleton() {
            super();
        }
    
        public static VolatileSingleton getInstance() {
            if (null == singletonInstance) {
                synchronized (SyncCodeSingleton.class) {
                    if (null == singletonInstance) {
                        singletonInstance = new VolatileSingleton();
                    }
                }
            }
            return singletonInstance;
        }
    
    }
    View Code

      其中,singletonInstance = new VolatileSingleton();在代码层面,这只是一行代码,但是JVM在执行改行代码的时候,会分三步执行

    1.   分配内存
    2.   使用VolatileSingleton的构造函数来初始化实例对象
    3.   将singletonInstance指向分配的内存空间(执行完这步 singletonInstance 就不再为 null 了)

      在不使用volatile关键字修饰singletonInstance的前提下,JVM为了提高自身的效率,可能会进行指令重排序,互换第二步和第三步的执行顺序。

      假设线程A运行到singletonInstance = new VolatileSingleton();时,刚执行完第一步和第三步,还没来得及初始化实例对象,而此时的singletonInstance已然不是null了。这时,如果有线程B运行到首次判空的地方,判断结果自然为false,那么线程B就会返回一个没有初始化的singletonInstance,从而导致获取单例对象失败。

      使用volatile关键字修饰singletonInstance,禁止了指令重排序,就可以保证,如果singletonInstance不为null,singletonInstance必然已经经过了初始化。

    volatile不能确保原子性
    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(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }

      在上述代码中,多个线程并发执行increase()方法。increase()方法中的自增操作(inc++;)可以分为三步:读取inc变量值,inc变量值加一,更新工作内存和主内存中的inc变量值。然而Java的自增操作并不具备原子性,就有可能产生以下的情景:

      线程A运行到inc++;处,刚读取到inc变量的值(这必然是最新的值),还没来的及后续操作,cpu分配的时间片就用完了,切换到线程B运行inc++;线程B成功更新了inc变量的值,并将inc变量的值更新到了主内存中。当再次轮到线程A执行的时候,接着之前的操作继续执行,由于之前已经读取了inc变量的值,就不会再去读取,这就导致该线程中的inc变量值不再是最新的值。最后导致旧值+1,覆盖了最新值。

       解决方案:可以通过synchronized或lock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger。

  • 相关阅读:
    Force.com微信开发系列(二)用户消息处理
    Force.com微信开发系列(一) 后台配置
    【Android开发】之Fragment与Acitvity通信
    【Android开发】之Fragment重要函数讲解
    【Android开发】之Fragment生命周期
    【Android开发】之Fragment开发1
    【Android开发】之MediaPlayer的错误分析
    【Andorid开发框架学习】之Mina开发之服务器开发
    【Andorid开发框架学习】之Mina开发之客户端开发
    【Andorid开发框架学习】之Mina开发之Mina简介
  • 原文地址:https://www.cnblogs.com/517cn/p/10875041.html
Copyright © 2011-2022 走看看