提升--04---并发编程之---有序性---volatile两大作用


并发编程之有序性

在这里插入图片描述

经典案例1:


import java.util.concurrent.CountDownLatch;

public class T01_Disorder {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {

        for (long i = 0; i < Long.MAX_VALUE; i++) {
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            CountDownLatch latch = new CountDownLatch(2);

            Thread one = new Thread(new Runnable() {
                public void run() {
                    a = 1;
                    x = b;

                    latch.countDown();
                }

            });

            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;

                    latch.countDown();
                }
            });
            one.start();
            other.start();
            latch.await();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                System.err.println(result);
                break;
            }
        }
    }

}

在这里插入图片描述

分析:

在这里插入图片描述

只有先执行 x = b;和 y = a;后执行a = 1;b = 1;才有可能造成x=0,y=0的情况

为何要乱序?

在这里插入图片描述

  1. 在指令1等待返回的时间里,cpu可以先执行指令2的操作,
  2. 所以最后从结果来看,可能会出现指令2先完成,指令1后完成的情况.

乱序的原则:

  • as-if-serial:看上去像是序列化(单线程)

在这里插入图片描述

单个线程,两条语句,未必是按顺序执行
单线程的重排序,必须保证最终一致性

经典案例2:

public class T02_NoVisibility {
    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {
        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) throws Exception {
        Thread t = new ReaderThread();
        t.start();
        number = 42;
        ready = true;
        t.join();
    }
}

2大隐患问题:

  1. 给标志位ready 加上volatile 保证线程的可见性
  2. 有可能先执行ready = true;输出number=0

线程乱序会引发的问题

对象的半初始化状态

  1. new 的时候,对象半初始化
  2. m成员变量赋 ,默认值0

在这里插入图片描述

  1. 调用构造方法,成员变量赋初始值m=8
  2. 变量t指向new对象地址

在这里插入图片描述

看一个例子: this溢出


public class T03_ThisEscape {

    private int num = 8;


    public T03_ThisEscape() {
        new Thread(() -> System.out.println(this.num)
        ).start();
    }

    public static void main(String[] args) throws Exception {
        new T03_ThisEscape();
        System.in.read();
    }
}

有可能输出num=0

分析:

在这里插入图片描述
仔细看汇编码指令:

  1. 指令—4 invokespecial 和指令— 7 astore_1,有可能乱序执行
  2. 即成员变量还没完成显性初始化的时候,变量 t (也可以看做this关键字),就已经指向半初始转态的对象了.
  3. 半初始转态的对象,只完成了默认初始化,属性num赋默认值0

在这里插入图片描述

结论 : 不要在构造方法中启动线程,可以new
public class T03_ThisEscape {

    private int num = 8;
    Thread t;

    public T03_ThisEscape() {
      t= new Thread(() -> System.out.println(this.num)
        );
    }

    public void run(){
        t.start();
    }

    public static void main(String[] args) throws Exception {
        T03_ThisEscape t03_thisEscape = new T03_ThisEscape();
        t03_thisEscape.run();

    }
}

哪些指令可以互换顺序?

在这里插入图片描述

cpu级别:

不影响单线程的最终一致性

java— JVM级别:

hanppens-before原则(JVM规定重排序必须遵守的规则)

JVM规定重排序必须遵守的规则
在这里插入图片描述

怎样阻止乱序执行?

cpu级别:

内存屏障是特殊指令:看到这种指令,前面的必须执行完,后面的才能执行
  • intel : lfence sfence mfence(CPU特有指令)

java— JVM级别:

在这里插入图片描述

volatile的底层实现

volatile两大作用:

1.保持线程可见性
2.禁止指令的重排序

在这里插入图片描述

hotspot实现

bytecodeinterpreter.cpp

int field_offset = cache->f2_as_index();
          if (cache->is_volatile()) {
            if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
              OrderAccess::fence();
            }

orderaccess_linux_x86.inline.hpp

inline void OrderAccess::fence() {
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

  • LOCK 用于在多处理器中执行指令时对共享内存的独占使用。
  • 它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。
  • 另外还提供了有序的指令无法越过这个内存屏障的作用。

1:保证线程的可见性

使用volatile,将会强制所有线程都会去堆内存中读取running的值
  1. 大家知道java里面是有堆内存的,堆内存是所有线程共享里面的内存,除了共享的内存之外呢,每个线程都有自己的专属的区域,都有自己的工作内存,如果说在共享内存里有一个值的话,当我们线程,某一个线程都要去访问这个值的时候,会将这个值copy一份,copy到自己的这个工作空间里头,然后对这个值的任何改变,首先是在自己的空间里进行改变,什么时候写回去,就是改完之后会马上写回去。什么时候去检查有没有新的值,也不好控制。=
  2. 在这个线程里面发生的改变,并没有及时的反应到另外一个线程里面,这就是线程之间的不可见 ,对这个变量值加了volatile之后就能够保证 一个线程的改变,另外一个线程马上就能看到

2:禁止指令重新排序

  • 指令重排序也是和cpu有关系,每次写都会被线程读到,加了volatile之后。cpu原来执行一条指令的时候它是一步一步的顺序的执行,但是现在的cpu为了提高效率,它会把指令并发的来执行,第一个指令执行到一半的时候第二个指令可能就已经开始执行了,这叫做流水线式的执行。在这种新的架构的设计基础之上呢想充分的利用这一点,那么就要求你的编译器把你的源码编译完的指令之后呢可能进行一个指令的重新排序。
  • 这个是通过实际工程验证了,不仅提高了,而且提高了很多。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值