zoukankan      html  css  js  c++  java
  • Volatile

    Volatile

    标签(空格分隔): 进程/线程 操作系统


    Java虚拟机提供的轻量级的同步机制

    1. 保证可见性

    不同的线程进入共享内存中读取数据之后, 在各自的工作空间对数据一通操作, 然后写入共享内存中, 这个时候因为共享内存的数据改变, 这个时候会通知其他读取该共享变量的线程, 通知该数据已经改变.

    /**
     * 1. 验证Volatile的可见性.
     * 1.1 加入number=0; number没有添加Volatile关键字修饰---没有可见性.
     * 1.2 在该线程对其私有虚拟机栈中栈帧中的备份number操作之后, 不会将数据覆盖到主内存当中.
     * 2. 加入Volatile之后
     * 2.1 在线程对其备份修改完毕之后, 会将数据覆盖到主内存当中.
     */
    public class VolatileDemo {
        public static void main(String[] args) {
            val myData = new MyData();
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "	 come in");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                myData.addTo60();
                System.out.println(Thread.currentThread().getName() + "	 updated number value: " + myData.number);
            }, "aaa").start();
    
            while (myData.number == 0) {
    
            }
    
            System.out.println("任务结束, number: " + myData.number);
    
        }
    }
    class MyData {
    //    volatile  int number = 0;
        int number = 0;
    
        public void addTo60() {
            this.number = 60;
        }
    
        public void addPlusPlus() {
            this.number++;
        }
    }
    

    2. 不保证原子性

    /**
     * Volatile 不保证原子性
     *
     * 在多个线程对栈帧中的number修改完毕之后, 在A线程马上开始写入主内存的时候被打断了, 这个时候B线程把自己的计算结果写入了
     * 这个时候, 就会产生计算结果被覆盖的情况. 然后永远都计算不出来正确的值.  这个就是Volatile的原子性问题.
     */
    public class VolatileDemo1 {
        public static void main(String[] args) throws InterruptedException {
            MyData myData = new MyData();
    
    
            for (int i = 0; i < 20; i++) {
    
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        myData.addPlusPlus();
                    }
                }, String.valueOf(i)).start();
    
            }
    
            /* main线程   和  后台GC线程 */
            while(Thread.activeCount()>2){
                Thread.yield();
            }
            System.out.println(myData.number);
            System.out.println("剩余线程数量"+ Thread.activeCount());
        }
    }
    class MyData {
    
        /* 通过 java.util.concurrent.atomic 包去 实现原子性操作. 自旋锁 
        * 可以看到AtomicInteger构造函数发现 最后存储的方式 private volatile int value;
        * */
        AtomicInteger atomicInteger = new AtomicInteger(1);
    
        public int addAtomic(){
            return atomicInteger.getAndIncrement();
        }
    }
    

    3. 禁止指令重排

    指令队列在CPU执行时不是串行的, 当某条指令执行时消耗较多时间时, CPU资源足够时并不会在此无意义的等待, 而是开启下一个指令. 开启下一条指令是有条件的, 即上一条指令和下一条指令不存在相关性. 例如下面这个例子:

    a /= 2;   // 指令A
    a /= 2;   // 指令B
    c++;      // 指令C
    

    这里的指令B是依赖于指令A的执行结果的, 在A处于执行阶段时, B会被阻塞, 直到A执行完成. 而指令C与A/B均没有依赖关系, 所以在A执行或者B执行的过程中, C会同时被执行, 那么C有可能在A+B的执行过程中就执行完毕了, 这样指令队列的实际执行顺序就是 C->A->B 或者 A->C->B.

    可能出现指令重排导致问题的代码

    public void method1()  {
        a = 1;                  // 语句1
        flag = true;            // 语句2
    }
    
    public void method2() {
        if (flag) {
            a = a + 5;
        }
    }
    

    工作区域和主内存出现的同步延迟现象导致的可见性问题可以使用synchronize或volatile解决. 他们都可以使一个线程修改后的变量立即对其他线程可见.

    运算指令

    运算由运算器单元(ALU)实现,指令包括算术运算指令、逻辑运算指令和移位指令。
    算术运算指令实现加减乘除(+-*/)等基本的算术运算;逻辑运算指令实现与或非(&|~)等基本的逻辑运算;移位指令实现二进制比特位(bit)的左右移(<<>>)运算。

    控制指令

    除了做计算外,CPU还要实现循环。循环是由跳转指令实现的,跳回去执行就是循环。循环在一定条件下跳出,否则就成死循环了,条件跳转指令能完成这个功能。条件跳转指令在一定条件下实现跳转,它能实现分支功能。跳转指令也称为控制指令。控制由CPU控制器单元实现。

    数据传送指令

    运算和控制指令的操作数从哪里来的呢?操作数都放在存储器中。在x86 IA中,运算指令的操作数既可以是寄存器,也可以是存储器;而在其他RISCIA例如MIPS中,运算指令的操作数只能是寄存器,因此需要先使用加载(load)指令将存储器中的数据导入到寄存器中,运算完成后,再用存储(store)指令将寄存器中的运算结果数据导出到存储器中。这类指令就是数据传送指令。


    可见性的实现就是 数据传送指令中的先使用加载(load)指令将存储器中的数据导入到寄存器中,运算完成后,再用存储(store)指令将寄存器中的运算结果数据导出到存储器中。


  • 相关阅读:
    redis下载安装及php配置redis
    php--小数点问题
    php--0与空的判断
    php--判断是否是手机端
    php--ip的处理
    mysql--sql_mode报错整理
    mysql-建表、添加字段、修改字段、添加索引SQL语句写法
    Python-多任务复制文件夹
    Python学习笔记(十一)——赋值、深拷贝与浅拷贝
    Python学习笔记(十)—JSON格式的处理
  • 原文地址:https://www.cnblogs.com/A-FM/p/11650312.html
Copyright © 2011-2022 走看看