zoukankan      html  css  js  c++  java
  • 关于Java中的volatile关键字的总结,适合了解不太多的人

    1、简介

    volatile是Java提供的一种轻量级的同步机制。

    Java包含两种内在同步机制:同步代码块(同步方法块) 以及 volatile关键字。

    volatile相比于synchronized(重量级锁),volatile不会引起线程上下文的切换和调度,但是其同步性差,稳定性也不高

    2、并发的三个基本概念(先介绍使用volatile的前提)

    (1)原子性

    关于原子性的定义

       ①  一个操作或者多个操作,要么全部执行,且执行过程中不会被任何因素打断。要么就全部都不执行

       ② 原子性是不支持多线程操作的,无论是多核还是单核,具有原子性的量同一时刻只能有一个线程对该量操作。

       ③ 整个操作过程中不会被线程调度器中断的操作,均可被认为是原子性(注:a++和++a不是原子性操作)

    属于原子性操作的范围:

     ① 基本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间不是原子操作

     ② 所有引用(reference)的操作:强引用(常用引用)、弱引用(debug之类)、软引用等

     ③ java.util.concurrent.Atomic.* 包中所有类的一切操作

    (2)可见性

    可见性的定义:

        ① 程序执行的顺序是按照代码的先后顺序执行(由上往下)

        ② 内存模型中的有序性可以理解为:在本线程观察,所有操作都是有序的(线程内表现为 “串行语义” )

       如果A线程观察B线程,则所有操作都是无序的(指令重排序、工作内存主内存同步延迟)

        ③ 可以通过synchornized和lock来保证有序性,synchornized和lock宝成每个时刻都是一个线程执行同步代码,

        相当于让线程有序地执行,保证了有序性

    (3)锁的互斥性和可见性

    锁的主要特性:互斥(mutual exclusion)和可见性(visibility)

        ① 互斥是一次只允许一个线程持有某个特定锁,一次只有一个线程能使用共享资源  
        ② 可见性则是要确保释放锁之前对共享资源的更改是对下一个获取锁的线程是可见性的,因为如果上一个线程
       的一些修改操作不开放给下一个线程的话,会导致看到的值不一致,这会引发很严重的问题
        ③ 线程安全的两个前提条件:对变量的write操作不依赖于当前的值。当前变量不包含在其它变量的不等式中
        ④ 以上的条件都是保证操作都是原子性操作,才能保证volatile在并发时能正确执行
        

    (3)Java的内存模型(JMM)以及共享变量的可见性

    注:JMM是一个抽象的内存模型,本地私有内存、主内存都是抽象概念,并非一定真实对应CPU缓存和物理内存

        ① JMM决定一个线程对共享资源的更改何时对下一个线程可见

        ② JMM定义了线程和主内存之间的抽象关系:共享资源存储在主内存中,每个线程都有本地的私有内存,
             本地私有内存保存了被该线程使用到的主内存副本的拷贝(记录)
        ③ 线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量

     (5)volatile关键字变量的特性

        ① 保证可见性,但不保证原子性

      Ⅰ.当写一个volatile变量时,JMM会把该线程本地私有内存中的变量强制刷新到主内存中去

      Ⅱ.该write操作会导致其他线程中的缓存无效

        ② 禁止指令重排(重排序是指编译器和处理器为了优化程序性能而对指令序列的一种方法,需以下条件)

      Ⅰ.重排序操作不会对有数据依赖关系的操作进行重排序,例如s = 1; b = s;这种有依赖关系的操作不重排序

      Ⅱ.重排序是为了优化性能,前提是不能改变单线程下的执行结果,例如a=1,b=2,c=a+b;过程可以重排序,结果不变

    (6)volatile关键字变量的原理

        ① volatile可以保证线程可见性有一定的有序性,但无法保证原子性(需用到内存屏障,会产生前缀命令lock)

      Ⅰ.确保重排序时不会将该变量后面的指令排序到内存屏障(即volatile变量)前,也不会将前面的指令放在其后面

      Ⅱ.会强制将本地私有缓存的变量刷新到主内存中

      Ⅲ.如果是写操作,会导致其它CPU对应的缓存行无效

     应用场景:重排序在单线程下一定可以保证结果的正确性,但是多线程下重排序操作则有可能会导致结果的非正确性

    public class MyVolatile {
        int a = 1;
        boolean status = false;
        
        //状态设置为true
        public void changeStatus(){
            
        /**
         *    以下两行并不存在依赖关系,
         *    重排序时可能存在先执行 status=true,再执行a=2
         */ 
            a = 2;  
            status = true; 
        }
        
        /**
         *    但此时已经判断了 status = true,则会进入下面的run方法判断
         *    因为重排序操作,a = 2可能还未执行,所以有可能 b = a + 1最终输出的结果还是2而不是3
         */
        
        
        //当状态为true,则运行
        public void run(){
            if(status){
                int b = a + 1;
                System.out.println(b);
            }
        }
    }

    解决方案:使用volatile关键字修饰共享变量阻止这种重排序,在编译时,volatile会在指令序列中插入内存屏障来禁止特定类型的处理器重排序

    volatile禁止重排序也有以下规则

    ① 当执行到volatile变量的读写操作时,在其前面的更改操作肯定已经全部执行,且结果也是对后面的操作可见的,

     而且在该变量后面的操作也一定没有执行(前面的搞定,后面获取军情后按兵不动)

    ② 在进行指令优化时,不能将volatile变量访问的语句放在其后面执行,也不能将后面的语句放在该变量前面执行

        也就是执行到volatile变量时,该变量前面所有语句全部执行完毕,该变量后面语句全部未执行

    不适用使用volatile关键字的场景:volatile不适合复合操作(重复操作)

    问题:myVolatile变量不是原子操作,可以由读取、加、赋值组成、所以结果不能达到3000

    // 不适合使用volatile的场景
    public class MySychronized {
        public volatile int myVolatile = 0;
        
        public void volatileCrease(){
            myVolatile++ ;
        }
        
        public static void main(String[] args){
            final MySychronized ms = new MySychronized();
            for(int i=0; i<10; i++){
                new Thread(){
                    public void run(){
                        for(int j=0; j<1000; j++){
                            ms.volatileCrease();
                        };
                    }.start();
                }
                while(Thread.activeCount() > 1)   //保证前面的线程执行完
                    Thread.yield();
                System.out.println(ms.volatileCrease);
                
            }
        }
    }

    解决方案:可以采用synchronized或者是Lock来解决这个问题,或者采用Java并发包中的原子操作类

    ① 采用synchronized

    // 使用sychronized
    public class Mysynchronized{
        public int mySource = 0;
        
        public synchronized void SynchronizedCrease(){
            mySource++;
        }
        
        public static void main(String[] args){
            final Mysynchronized ms = new Mysynchronized();
            for(int i=0; i<10; i++){
                new Thread(){
                    public void run(){
                        for(int j=0; j<1000; j++){
                            ms.SynchronizedCrease();
                        };
                    }.start();
                }
                while(Thread.activeCount() > 1)   //保证前面的线程执行完
                    Thread.yield();
                System.out.println(ms.volatileCrease);
            }
        }
    }

    ② 采用Lock

    //使用Lock
    public class MyLock{
        public int mySource = 0;
        
        public synchronized void LockCrease(){
            mySource++;
        }
        
        public static void main(String[] args){
            final MyLock ms = new MyLock();
            for(int i=0; i<10; i++){
                new Thread(){
                    public void run(){
                        for(int j=0; j<1000; j++){
                            ms.LockCrease();
                        };
                    }.start();
                }
                while(Thread.activeCount() > 1)   //保证前面的线程执行完
                    Thread.yield();
                System.out.println(ms.LockCrease);
            }
        }
    }

    ③ 采用Java并发包中的原子操作类,该类是通过CAS循环方式来保证原子性的

    //采用Java并发包的原子操作类
    public class MyAtomic{
        public int mySource = 0;
        
        public synchronized void AtomicCrease(){
            mySource++;
        }
        
        public static void main(String[] args){
            final MyAtomic ms = new MyAtomic();
            for(int i=0; i<10; i++){
                new Thread(){
                    public void run(){
                        for(int j=0; j<1000; j++){
                            ms.AtomicCrease();
                        };
                    }.start();
                }
                while(Thread.activeCount() > 1)   //保证前面的线程执行完
                    Thread.yield();
                System.out.println(ms.AtomicCrease);
            }
        }
    }

     应用场景:状态标记量

    // 状态标记量
    volatile
    boolean flag = false; while(!flag){ doSomething(); } public void setFlag() { flag = true; } volatile boolean inited = false; //线程 1: context = loadContext(); inited = true; //线程 2: while(!inited ){ sleep() } doSomethingwithconfig(context);

     应用场景:单例模式中使用双重锁

    // 单例模式中使用双重锁
    class
    Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }
  • 相关阅读:
    ubuntu安装QQ
    Ubuntu 14.04/14.10下安装VMware Workstation
    在Macbook上安装ubuntu
    MacBook Air密码忘了,苹果电脑密码忘了怎么办
    Win7下U盘安装Ubuntu14.04双系统
    linux紧急救援模式
    Standard Library Modules Using Notes
    【LeetCode】136 & 137 & 260
    Python获取脚本所在目录的正确方法【转】
    Autorun a python script after reboot using rc.local
  • 原文地址:https://www.cnblogs.com/liaoyuanping-24/p/14493200.html
Copyright © 2011-2022 走看看