Java并发JUC(九)

98 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情
本文主要介绍volatile的三大特性,包括可见性、不保证原子性、禁止指令重排,并举例来介绍。

17. volatile

可见性

  • 不加volatile程序会死循环,线程thread一直在执行,看不见num的变化
public class JMMDemo {
    //不加volatile程序会死循环,线程thread一直在执行,看不见num的变化
    private volatile static int num = 0;
    public static void main(String[] args) {//main
        new Thread(()->{ //线程1 对主内存对变化不知道(加个vloatile)
            while (num == 0){
​
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
    }
}

不保证原子性

  • num++不是一个原子操作,具体与它的底层实现有关。对于num++,cpu是先拿到这个静态变量,然后放入累加器执行+1操作,接着将加1之后的结果放到num变量原先的内存地址中,但在线程A拿到累加器的同时,线程B也有可能拿到,在A、B的线程执行的时候,各自的累加器中的数值是不同的,就导致了num被两次更新,值却不是2。
  • 针对原子性的保证,可以使用lock和synchronized。
  • 还有一个JUC中的保证原子性的方法,已经在程序中写到了。
  • Atomic原子类来保证原子性。AtomicInteger的加一方法,用的是底层的CAS效率极高。
public class VDemo02 {
//    private volatile static int num = 0;
    private volatile static AtomicInteger num = new AtomicInteger();
    public static void add(){
//        num++;//不是一个原子性操作
        num.getAndIncrement();//这个是AtomicInteger的加一方法,用的是底层的CAS效率极高
    }
    public static void main(String[] args) {
        //理论上num应该位20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        //判断线程有没有执行完
        while (Thread.activeCount() > 2) { //这个2表示的是main和gc
            Thread.yield();
        }
        //但结果不是两万,因为没有加上原子性操作。
        System.out.println(Thread.currentThread().getName() + " " + num);
​
    }
}

18. volatile禁止指令重排

计算机不是按照你“写的程序”来执行的。源代码->编译器优化重排->指令并行也可能会重排->执行

解释“上面这句话”:假设我们写了四行代码1:int x = 1;2:int y = 2;3:x = x + 5;4:y = x * x;我们想到的执行顺序就是1234,但程序中执行顺序也肯可能是2134、2143(进行了重排)。

对于上述例子就有多种结果x = 6,y=36; x = 6,y=1。

当然处理器对于上述例子不会执行4123这样的顺序,因为处理器进行指令重排的时候是有规则的,它会考虑数据之间的依赖性! 当然这种情况可能在程序上很难遇见,但在逻辑上是很正常的。

volatile可以避免指令重排:内存屏障(禁止上面指令和下面指令顺序交换)。CPU指令。作用:

1.保证特定的操作执行顺序!2.可以保证某些变量的内存可见性!(利用这些特性volatile实现了可见性。)

加了volatile如下:

image.png