Java多线程与并发_volatile关键字详解

Java多线程与并发_volatile关键字详解

仅仅活着是不够的,还需要有阳光、自由,和一点花的芬芳

一、volatile关键字

volatile是JVM提供的一种轻量级的同步机制,特性:

1.保证内存可见性

2.不保证原子性

3.防止指令重排序

二、JMM(Java Memory Model)

Java内存模型中规定了所有的变量都存储在主内存中(如虚拟机物理内存中的一部分),每条线程还有自己的工作内存(如CPU中的高速缓存),线程的工作内存中保存了该线程使用到的变量到主内存的副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存和工作内存的交互关系如下图所示:
在这里插入图片描述

三、验证

1.验证volatile的可见性

1.1 假如 int num = 0; num变量之前根本没有添加volatile关键字修饰,没有可见性
1.2 添加了volatile,可以解决可见性问题

MyData类

class MyData {
    volatile int num = 0;

    public void addT060() {
        this.num = 60;
    }
}

内存可见性验证,其中两个线程分别为AAA线程和main线程

    //volatile可以保证可见性,及时通知其它线程,主内存的值已经被修改
    @Test
    public void seeOkByVolatile() {
        MyData myData = new MyData();//资源类

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            //暂停一会线程
            try{
                TimeUnit.SECONDS.sleep(3);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addT060();
            System.out.println(Thread.currentThread().getName() + "\t update num value: " + myData.num);
        },"AAA").start();

        //第2个线程是我们的main线程
        while (myData.num == 0) {
            //main线程就一直在这里等待循环,直到num值不再等于0.
        }
        System.out.println(Thread.currentThread().getName() + "\t mission is over,main get num value: " + myData.num );
    }

对num变量加volatile修饰后结果

AAA	 come in
AAA	 update num value: 60
main	 我能见到AAA线程对num修改的结果啦,main get num value: 60

Process finished with exit code 0

2.验证volatile不保证原子性

2.1 原子性指的是什么意思?
不可分割,完整性,也即某个线程正在做某个具体任务时,中间不可以被加塞或者被分割。需要整体完整。要么同时成功,要么同时失败。
2.2 volatile不保证原子性的案例演示
2.3 为什么不保证原子性?
2.4 如何保证原子性
加sync
使用我们juc下的AtomicInteger (底层实现CAS)

给MyData类加addPlusPlus()方法

class MyData {//MyData.java ===> MyData.class ===> JVM字节码
    int num = 0;

    public void addT060() {
        this.num = 60;
    }

    //请注意,此时num前面是加了关键字修饰的,volatile不保证原子性
    public void addPlusPlus() {
        num++;
    }
}
2.2 volatile不保证原子性的案例演示

num++在多线程操作的情况下不保证原子性的

创建20个线程并行执行num++操作2000次,多次测试,结果不为40000

public static void main(String[] args) {
        MyData myData = new MyData();

        for (int i = 1; i <= 20; i++ ) {

            new Thread(() -> {
                for (int j = 1; j <= 2000; j++) {
                    myData.addPlusPlus();
                }

            },String.valueOf(i)).start();
        }

        //需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值看是多少?
        while(Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t finally num value:" + myData.num);
    }

结果:数值小于40000,出现写值丢失的情况

main	 finally num value:38480

Process finished with exit code 0
2.3 为什么不保证原子性?

因为当线程A对num++操作从自己的工作内存刷新到主内存时,还未通知到其他线程主内存变量有更新的瞬间,其他线程对num变量的操作结果也对主内存进行了刷新,从而导致了写值丢失的情况

num++通过汇编指令分析,通过javap反编译得到如下汇编指令

class com.slx.juc.MyData {
  volatile int num;

  com.slx.juc.MyData();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2                  // Field num:I
       9: return

  public void addT060();
    Code:
       0: aload_0
       1: bipush        60
       3: putfield      #2                  // Field num:I
       6: return

  public void addPlusPlus();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2                  // Field num:I
       5: iconst_1
       6: iadd
       7: putfield      #2                  // Field num:I
      10: return
}

可见num++被拆分成了3个步骤,简称:读-改-写

  1. 执行getfield拿到原始num;
  2. 执行iadd进行加1操作;
  3. 执行putfield写把累加后的值写回
2.4 如何保证原子性
加sync
使用我们juc下的AtomicInteger (底层实现CAS)

MyData类中添加原子类操作方法

    AtomicInteger atomicInteger = new AtomicInteger();
    public void addMyAtomic() {
        atomicInteger.getAndIncrement();
    }

调用该方法打印结果

    public static void main(String[] args) {
        MyData myData = new MyData();

        for (int i = 1; i <= 20; i++ ) {

            new Thread(() -> {
                for (int j = 1; j <= 2000; j++) {
                    myData.addMyAtomic();
                }

            },String.valueOf(i)).start();
        }

        //需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值看是多少?
        while(Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type ,finally num value:" + myData.atomicInteger);
    }

测试结果为40000,不会出现之前int类型的丢失值的情况

main	 AtomicInteger type ,finally num value:40000

Process finished with exit code 0
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值