• Java内存模型与volatile关键字


    Java内存模型与volatile关键字

    一)、并发程序开发

    并行程序的开发要涉及多线程、多任务间的协作数据共享问题。

    常用的并发控制:内部锁、重入锁、读写锁、信号量。

    二)、线程的特点

    线程的特点:

    1).每一个线程都有一块工作内存区,存放所有线程共享的主内存中变量值的拷贝。

    2).当程序执行时,它在自己的工作内存中操作这些变量。

    3).线程操作,先获取锁定并清除它的工作内存,重新将共享内存区正确的装入线程

    ​ 的工作内存区。

    4).线程解锁,将线程工作内存区的值重新写回共享内存中。

    注:线程的内存操作围绕着线程工作内存区和共享内存区。

    三)、线程的操作

    线程中java的内存模型:

    线程引擎

    线程工作内存

    主内存

    一个线程可以执行的操作

    use: 将变量在线程工作内存中的拷贝传送给线程执行引擎。

    assign: 将值赋给线程工作内存对应的变量

    load: 把read操作从主内存读取的值放入线程工作内存空间

    store: 将线程工作区内存的变量内容传送到主内存

    lock:

    unlock:

    主内存可以执行的操作:

    read: 把主内存中变量的拷贝内容传送到线程工作内存。

    write: 把store操作从线程中得到的值放入主内存的变量拷贝。

    lock: 主内存的lock操作使线程获得一个独占锁。

    unlock: 主线程的unlock操作使线程释放一个独占锁。

    注意事项:

    1).use、assign、lock、unlock操作都是线程的执行引擎和线程工作内存的原子操

    ​ 作:

    2).主内存和线程工作内存间的数据传送并不满足原子性:

    ​ i: 将主内存的数据复制到工作内存区,需要进行两个动作

    ​ 首先,通过read操作,将主内存中变量的拷贝内容输入到工作内存中,再通过

    ​ load操作,将读取都内如写到工作内存区中。

    ​ ii: 从线程工作主内存中写入数据到主内存

    ​ 首先,通过store操作将线程工作内存中变量的内容输入到主内存中,再通过

    ​ write操作,将输入的数据写入到主内存。

    3).主内存和线程工作内存之间的数据传送需要一定的时间,且消耗的时间不同:

    ​ 当线程对变量a进行赋值,再给变量b赋值,在另一个线程中,可能先在主内存中

    ​ 先看见b 的更新,再看见变量a的更新,此时,出现了变量改变,主内存的值更

    ​ 新不及时的情况。

    原因:主线程和工作区的数据传送需要一定的时间,且所需时间不同,当变量做

    ​ 出了改变,不能很快的将改变的值反应到主内存中,线程中共享变量的值

    ​ 都是从主内存中取出的。

    4).线程的每一个read操作都跟随着一个load操作,每一个store操作都跟着一个

    ​ write操作。

    四)、double和long类型的非原子变量处理

    double 和 long类型的特点:

    在变量进行write和read操作时,主内存把它当做两个32位的read或write操作处理,且这两个操作在时间上是分开的,对第一个32位进行操作后,还有其它的操作介于它们之间。

    举例:

    两个并发的线程对共享的double或long型变量赋不同的值,最后变量得到的值可能

    不等于任何一个线程所赋的值,而可能是依赖于具体应用的两个线程所赋值的混合

    赋值分析:

    第一个同步线程对double的第一个32位进行赋值,输入到线程工作内存,中间隔着一系列操作,此时第一个32位可能已经写入了主内存,随着第二个32位也完成了赋值,但未写入都主内存,紧着这第二个线程获取到锁,对同一个变量进行操作,开始第一个32位的赋值操作,并将这个32位写入主内存,此时,double能量的值是两个同步线程所赋值的混合。

    改进:必须对double或long进行同步,使用volatile关键字。

    五)、 Volatile关键字

    为什么要使用volatile关键字?

    每个线程都有自己的工作区,当一个线程改变自己工作内存中的数据时,对其他线程来说是不可见的,使用volatile关键字,迫使所有的线程均读写主内存中对应变量,从而使得volatile变量在多线程间可见。

    volatile的作用:

    1).其他线程对变量的修改,可以及时反应在当前线程中。

    ​ 原因:使用volatile标识变量,迫使所有线程均读写主内存中对应的变量

    2).确保当前线程对volatile变量的修改,能及时写回共享主内存中,并被其他线程

    ​ 所见。

    3).使用volatile声明的变量,编译器保证其有序性。

    六)、使用例子来说明Volatile关键字的作用

    构建两个线程,操作同一个VolatileTest02的变量isExist,一个线程根据 isExist == !isExist来判断程序是否执行退出操作,另一个不断切换isExist值的。

    Boolean is isExist = false;

    tryExist()

    swapExist()

    import org.junit.Test;
    
    /**
     * 使用一个boolena型变量,isExist来验证Volatile
     *
     *
     */
    public class VolatileTest02 {
        private Boolean isExist = false;
    
         /**
         * 尝试退出线程
          * 读取非Volatile修饰的变量,从当前的工作线程空间读取,不能及时的发现另一个线程对变量值isExist的修改
          *
        */
        public void tryExist(){
            if(isExist == !isExist){
                System.out.println("2");
                System.exit(0);
            }
        }
    
        /**
         * 更改isExist的值
         * 当isExist的值修改时,被写入主内存中。
         * 此时,mainThread线程的线程工作内存未能及时的读取isExist的更改,没有将改变值load入线程工作内存,isExist == !isExist很难成立
         *
         */
        public void swapVlaue(){
            isExist = !isExist;
        }
    
        /**
         * 创建两个线程,一个线程用于不断的执行是否退出操作,另一个线程不断的更改isExist的值
         */
        @Test
        public void test() throws InterruptedException {
            final VolatileTest02 volObj = new VolatileTest02();
            Thread mainThread = new Thread(){
                @Override
                public void run(){
                    //不断判断isExist的值,如果isExist = !isExist,程序退出
                    System.out.println("mainThread start");
                    //线程不断的尝试退出
                    while(true){
                        volObj.tryExist();
                    }
    
                }
            };
            mainThread.start();
    
            Thread swapThread = new Thread(){
                public void run() {
                    System.out.println("swapThread start");
                    //线程不断的修改isExist的值
                    while (true) {
                        volObj.swapVlaue();
                        System.out.println(1);
                    }
    
                }
            };
            swapThread.start();
            Thread.sleep(1000);
        }
    }
    
    

    案例分析:

    mainThread线程:

    开启该线程,判断isExist == !isExist, 因为读取isExist变量是从当前线程的工作内存中读取,虽然使用tryExist()不断改变isExist的值,但不是对mainThread线程立即可见的,期间还要经历read/load过程。

    swapThread线程:

    开启改线程,tryExist(),isExist = !isExist,不断改变isExist的值,将值写入到主内存

    当该线程的isExist被修改时,不能及时的反应到mainThread中。

    使用volatile关键字:

    线程直接从主内存中读取数据,isExist == !isExist, 当读取等式的左边数据后,右边数据前,isExist值及有可能已经被修改,此时isExist == !isExist成立。

    volatile关键字的作用:

    1).确保当前线程对volatile的更改,能及时的写回共享主内存。

    2).其他线程对变量的修改,可以及时反映在当前线程中。

    3).使用volatile声明的变量,保证其有序性

    ​ int number = 0;

    int stop = false;

    未使用volatile可能会先将stop的值写入到,再将number值写入

    金麟岂能忍一世平凡 飞上了青天 天下还依然
  • 相关阅读:
    Maven+SpringMVC+Mybatis 开发环境整合
    在子jsp页面中调用父jsp中的function或父jsp调用子页面中的function
    动态库的生成和调用
    怎么下载纯净版系统
    ATL开发COM组件
    链表问题
    内存理解
    静态绑定和动态绑定;位拷贝和值拷贝
    导EXCEL单表单方法
    mfc解决回车键默认关闭窗口的一般方法
  • 原文地址:https://www.cnblogs.com/Auge/p/11752777.html
Copyright © 2020-2023  润新知