Java JUC并发之Volatile的可见性以及非原子性验证

十七、Volatile 的可见行 和非原子性验证

什么是volatile ?

  • volatile 是一种轻量级的同步机制,相对于synchronized来说

  • 保证可见性 => JMM 主内存中的共享变量修改之后,会通知所有线程备份到各自的工作内存中

  • 不保证原子性

  • 禁止指令重排

保证可见性

package com.liu.volatiletest;

import java.util.concurrent.TimeUnit;

/**
 * 验证Volatile的可见性
 */
public class VolatileDemo {
    private volatile static int num = 0;
    public static void main(String[] args) {
        new Thread(()->{ // 线程1
            while (num == 0) {

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

不保证原子性

什么是原子性?

  • 原子性指的是一组操作是不可分割的,要么全部执行,要么全部不执行
  • All or Nothing !
package com.liu.volatiletest;

public class VolatileDemo02 {

    private static int num = 0;
    public  static void add() {
        // num++;  => 不是原子操作

        //CAS 实现原子操作
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        // 理论结果应该为20000
        for (int i = 1; i <= 20; i++) {

            new Thread(()->{

                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2 ){ // main gc
            Thread.yield(); // 礼让 直到其他线程都执行完了,才继续执行main
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

既然Volatile不能保证原子性,那还有其他解决方案来保证原子性吗?

解决方案:

synchronized 或 lock

如果不使用synchronized 或 lock来同步(那样会降低性能),还有没有其他方法?

使用原子类 AtomicInteger

package com.liu.volatiletest;

import java.util.concurrent.atomic.AtomicInteger;
public class VolatileDemo02 {
    private static AtomicInteger num = new AtomicInteger();

    public  static void add() {
        //底层 CAS 实现原子操作
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        // 理论结果应该为20000
        for (int i = 1; i <= 20; i++) {

            new Thread(()->{

                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2 ){ // main gc
            Thread.yield(); // 礼让 直到其他线程都执行完了,才继续执行main
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}
// 原子类底层 使用了Unsafe类
private static final Unsafe U = Unsafe.getUnsafe();

原子类的底层都直接与操作系统挂钩!(Java无法直接接触操作系统)

Unsafe类是一个很特殊的存在!

什么是指令重排

自己编写的程序,计算机底层并不是按照自己写的那样去执行代码的

源代码 -> 编译器优化的重排 -> 指令并行也可能会重排 -> 内存系统重排 -> 执行

处理器在进行指令重排的时候,要考虑数据之间的依赖性!

int x = 1; // 1
int x = 2;// 2
x = x + 5;// 3
y = x * x;// 4

期望执行顺序 : 1234
   但是可能执行的时候变成:2134 1324
    不可能是 4123!

可能造成影响的结果:

a b x y 默认为 0

线程A 线程B
x = a y = b
b = 1 a = 2

正常的结果 : x = 0 ;y = 0;

但是由于指令重排

线程A 线程B
b = 1 a = 2
x = a y = b

会导致得不到预期的结果 :x = 2 ; y = 1

volatile 可以避免指令重排

内存屏障 => CPU指令

作用:

  • 保证特定的操作的执行顺序
  • 可以保证某些变量的内存可见性

利用这几个特性,Volatile实现了可见行

总结:

Volatile 可以保证可见性,但是不能保证原子性

由于内存屏障,可以避免指令重排的现象产生

posted @ 2021-07-16 00:34  夕立君  阅读(58)  评论(0编辑  收藏  举报