zoukankan      html  css  js  c++  java
  • 多线程的共享变量的内存不可见性

    转:https://www.cnblogs.com/huangleshu/p/10026222.html

    /**
     * 线程的开销 :  线程的创建和销毁
     *              线程的上下文切换和调度
     *              线程的同步
     *
     *
     * 多线程的内存模型: 线程独有的工作内存(线程缓存用于提高效率)---------所有线程共享的主内存
     *
     * 线程读取在主内存的成员变量(即共享变量)的过程:
     * 1. 线程的工作内存会去读取主内存的成员变量并保存副本
     * 2. 线程在工作内存中修改副本
     * 3. 将修改后的副本的值推送给主空间并改写主空间该成员变量的值
     * 4. 主空间成员变量修改后的值将不会主动推送给其他线程, 这就造成了线程的工作内存的共享变量的不同步
     *
     * 问题: 各个线程的工作内存不可见
     * 即 A线程先读取共享变量a, B线程修改了共享变量a后为a`,推送给主内存并改写, 主内存不会推送给A线程,A和B的变量会不同步
     *
     * 解决办法
     * synchroized可以同步值
     * volatile关键字 会使得主内存的共享变量每经过一次改变都会推送给其他的线程, 其他线程会修改其副本
     *
     * 同步值之synchronized和volatile的区别
     * 相同点:
     * synchronized 和 volatile都能用来同步共享变量
     * 不同点:
     * 1. volatile是轻量级的同步策略, 可以修饰基本类型的变量,如int, synchronized是重量级的同步策略,基于对象的同步锁
     * 2. volatile不具备互斥性, 一个线程访问共享变量 , 其他线程也可以访问共享变量
     *    synchronized是互斥锁, 具备互斥性, 在被锁的代码块上只能有一个线程访问共享变量
     *
     * 3. volatile不能保证变量的原子性, 即一组对共享变量的操作不具备事务(要么全部完成,要么全部不完成) 如 i++/i--
     *    即一个线程在进行一组操作中还没完成时, 其他线程也能进入这组操作对共享变量进行修改
     *    而 synchronized则能保证一组对共享变量操作的原子性, 即这组操作全部完成,才能进行下一轮操作
     *     即在被锁的代码块中只能允许一个线程去执行这组操作, 其他需要执行这组操作的线程会进入阻塞状态,等待其完成
     *
     * 总结: 主内存    工作内存
     *      共享变量   副本
     *      工作内存中会主动去拉去主内存的共享变量并创建其副本
     *      工作内存中的副本修改后会推送给主内存改写共享变量
     *      volatile 会使得主内存修改后的共享变量推送其他线程
     *
     * 内存不可见的本质 : 线程之间有互相独立的缓存即, 当多个线程对共享数据进行操作时, 其操作彼此不可见
     *
     * 可以直接理解: 使用volatile之后该共享该变量线程不在工作内存缓存其副本, 所有线程对该变量的操作全是在主内存中完成
     *        即不在存在操作的不可见,所有线程的操作的变量是位于主内存的变量
     */
    
    public class VolatileTest {
        public static void main(String[] args) throws InterruptedException {
            MyTask task = new MyTask();
            new Thread(task).start();
    
            while (true){
                //读取值
                //直接用线程缓存的值 不会去主内存去拉取变量
                if (task.isFlag()){
                    System.out.println("=============");
                    break;
                }
    
                /*
               synchronized (task){
                    if (task.isFlag()){
                        System.out.println("=============");
                        break;
                    }
                }*/
    
            }
        }
    
    }
    
    
    
    class MyTask implements Runnable{
    
        private volatile boolean flag = false;
    
        // 修改值
        @Override
        public void run() {
    
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            System.out.println("flag=" + flag);
        }
    
        public boolean isFlag() {
            return flag;
        }
    }
    
    
    /**多线程之数值运算
     * i++的原子性问题:
     *  int i=10;
     *  int result = i++;
     *  结果result为10 ?
     *  i++的实现步骤 : 读-改-返回值并写入
     *  1. int temp = i
     *  2. i = i + 1;
     *  3. return temp 即 result = temp
     *  i++和 ++i的区别是第3步: ++i  return i  即 result = i
     *
     * i++的多线程操作的问题:会产生重复数据
     *即在一个线程的工作内存对i的副本进行自增,但是没有推送给主内存更新i, 这是其他线程也读取了未更新i值
     *本质 一组操作的原子性
     *
     * volatile的不能保证i++操作同步的原因
     * i++有读-改-写3步操作 ,需要保证这3个操作的原子性 ,
     * volatile只能保证副本之间的可见性, 即volatile保证读-改-写的操作的共享变量是对主内存的变量i进行操作
     * 不能保存多个操作的原子性,即在进行读-改-写这组3步操作时,其他线程也能进如这组操作
     * 在写阶段即返回赋值前,其他线程会读取到未修改之前的i 这样多个线程会出现重复数据
     *
     * 理解: 多线程:
     *          1. 考虑操作是否具有原子性,即底层的单个操作或多个操作
     *          2. 操作具有原子性即单个操作(如赋值)则使用volatile
     *          3. 操作不具有原子性即多个操作,则使用互斥的同步锁
     *
     * 使用场景 多线程中  原子性操作(不可分割的操作 比如赋值)可以直接用volatile进行数据同步
     *                  多个操作可以分割(非原子性操作)必须选用同步锁的互斥性保证这组操作的原子性(多个操作不可分割)
     *
     *  多线程i++的解决办法: java.util.concurrent.atomic包下的原子类:
     *          1. volatile修饰属性保证其原子的可见性
     *          2. CAS(compare and swap)比较替换算法保证其原子性
     *          CAS算法是硬件对于并发操作共享数据的支持
     *          CAS包括是三个操作数: V内存值 A预估值(更新前会再次读取主存中的值) B更新值
     *          步骤:  if (V == A) V=b; 即通过比较来确认替换前的主存的值是否被修改, 只没有修改时才替换更新值
     *          (比同步锁效率要高, 即在多线程中,对数值的计算(包括++ --)操作优先使用原子类)
     */
    public class AtomicTest {
    
        public static void main(String[] args) {
            AtomicDemo ad = new AtomicDemo();
    
            for (int i = 0; i < 10; i++) {
                new Thread(ad).start();
            }
        }
    
    }
    
    class AtomicDemo implements Runnable{
    
    // private volatile int serialNumber = 0;
    
        private AtomicInteger serialNumber = new AtomicInteger(0);
    
        @Override
        public void run() {
    
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
    
            System.out.println(getSerialNumber());
        }
    
        public int getSerialNumber(){
    
            return  serialNumber.getAndIncrement();
    
    //        return serialNumber++;
        }
    
    
    }
    
    /**
     * 模拟CAS算法
     * CAS包括是三个操作数: V内存值 A预估值(更新前会再次读取主存中的值) B更新值
     * if (V==A) V=B
     */
    public class CompareAndSwapTest {
    
        public static void main(String[] args) {
            CompareAndSwap cas = new CompareAndSwap();
            for (int i = 0; i < 10; i++) {
                new Thread(){
                    @Override
                    public void run() {
                        //1. 获取内存值 memoryValue 和  更新值newValue
                        int memoryValue = cas.getValue();
                        System.out.println(cas.compareAndSet(memoryValue, new Random().nextInt(100)));
                    }
                }.start();
            }
        }
    }
    
    class CompareAndSwap{
        private int value;
    
        //2. 获取预期值 expectedValue 并和memoryValue比较, 相等就设置值, 返回预期值
        public synchronized int compareAndSwap(int memoryValue, int newValue){
            //保存内存值OldValue(V)
            int expectedValue = value;
            if (expectedValue == memoryValue){
                value = newValue;
            }
    
            //并回返回内存值OldValue(V)
            return expectedValue;
        }
    
        //3. 比较memoryValue 和 expectedValue,相等就返回成功标识true
        public synchronized boolean compareAndSet(int memoryValue, int newValue){
            return memoryValue == compareAndSwap(memoryValue,newValue);
        }
    
    
        public synchronized int getValue() {
            return value;
        }
    
    }
  • 相关阅读:
    JDBC 连接 MySQL 数据库
    通过java类的反射机制获取类的属性类型
    反射机制实例化类,并获取类中的属性、方法、和构造器
    java8u162反射机制的一个BUG
    Java反射关于getDeclaredMethods()和getMethods()的区别
    Java反射
    30天C#基础巩固------了解委托,string练习
    30天C#基础巩固------读写流(StreamWrite/StreamReader)
    30天C#基础巩固------集合,File(文件操作 ),Encoding处理字符集
    30天C#基础巩固------面向鸭子编程,关于string和File的练习
  • 原文地址:https://www.cnblogs.com/wllhq/p/13753544.html
Copyright © 2011-2022 走看看