JAVA并发编程实战-1可见性、原子性、有序性:并发编程BUG的源头

  • 关键词:性能
  1. 是什么:
    CPU运行速度、CPU从内存读写数据、IO设备从磁盘读写数据到内存,3者存在巨大的速度差异。CPU执行一天,相当于CPU从内存读写数据一年;读写内存一天,相当于IO设备读写十年。
    性能往往是由最短的木板决定的,基于此现状,计算机体系结构、操作系统、编译软件分别作了以下优化:
    1) 设置缓存,CPU读写缓存快于读写内存,可以提升CPU的使用率;这导致了可见性问题
    2) 设置多进程、多线程,分时复用CPU,均衡CPU和IO设备速度差异(这里不明白,是均衡CPU和内存还可以理解,还需要进一步深入);这引入了线程切换,导致了原子性问题
    3) 对代码进行重排序,增加缓存利用率,减少对内存、磁盘的读取;这导致了有序性问题;

  2. 缓存导致了可见性问题:
    课程中给出的例子是这个,感觉容易和原子性混淆,故而采用了另外的一个例子:

/**
 * 可见性测试
 * 测试0:
 *      测试方法test0:对于变量count使用volatile和不使用volatile的区别
 *
 * 测试1:
 *      测试方法test1:使用volatile不能保证原子性
 *
 * 测试2:
 *      测试方法test2:使用synchronized能保证原子性
 */
public class M1C1Test0 {

    static  volatile  int count = 300;
    public static void main(String[] args){
    //   test0();
     //  test1();
       test2();
    }

    /**
     * 测试0:对于变量count使用volatile和不使用volatile的区别
     */
    public static void test0(){
        new Thread(()->sonThreadFunction()).start();
        /**
         * 这里如果不sleep的话,该例子就不生效了,原因还未知
         */
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count = 1000;
        System.out.println("已置 count = " + count);
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("已置 count = " + count + ",又过了5秒");
    }

    public static void sonThreadFunction(){
        System.out.println("执行开始");
        while (count < 500){

        }
        System.out.println("执行完成");
    }

    /**
     * 测试1:使用synchronized能保证原子性
     */
    public static void test2() {
        Thread thread0 = new Thread(()->add100K0());
        thread0.start();
        add100K0();

        try {
            thread0.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正确值为" + 200300);
        System.out.println("实际值为" + count);
    }

    public static synchronized void add100K0(){
        for(int i = 0; i < 100000; i++){
            count ++;
        }
    }

    /**
     * 测试2:使用volatile不能保证原子性
     */
    public static void test1() {
        Thread thread0 = new Thread(()->add100K());
        thread0.start();
        add100K();

        try {
            thread0.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正确值为" + 200300);
        System.out.println("实际值为" + count);
    }

    public static void add100K(){
        for(int i = 0; i < 100000; i++){
            count ++;
        }
    }

}

1) test0的输出结果,线程A和线程B各自有独立的CPU缓存,值都是先从CPU缓存中读取,如果不设置volatile,在某些情况下(比如CPU缓存不够用)再将CPU缓存中的值flush到内存,不会马上写到内存,所以导致count置为1000后,对另外一个线程来说仍不可见。
参照:java中关于volatile的理解疑问?
在这里插入图片描述

count没有volatile:
执行开始
已置 count = 1000
已置 count = 1000,又过了5秒

count有volatile:
执行开始
执行完成
已置 count = 1000
已置 count = 1000,又过了5秒

2)测试2和测试3参照这个都能明白:java中关于volatile的理解疑问?

  1. 线程切换导致了原子性问题,在上面的例子中也有说。

4.编译优化带来的有序性问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值