多线程的共享变量的内存不可见性

转: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;
    }

}
原文地址:https://www.cnblogs.com/wllhq/p/13753544.html