Volatile学习

Volatile学习

JMM(java内存模型)

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中运行,首先要将变量从主内存中拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中变量,各个线程中的工作内存中存储着主内存中的变量的副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间通信(传值)必须通过主内存来实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AmvwipSH-1616739889774)(C:\Users\lty\AppData\Roaming\Typora\typora-user-images\image-20210325215907137.png)]

JMM三大特性:

1)可见性 2)原子性 3)有序性

Volatile关键字

Volatile是java虚拟机提供的一种轻量级的同步机制

特性:1)保证可见性 2)不保证原子性 3)禁止指令重排

1)保证了用volatile修饰的变量对所有线程的可见性

可见性:当一个线程修改了变量的值,新的值会立刻同步到主内存当中。
而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。

代码演示:

1)没有使用volatile

public class VolatileTest01 {
    public static void main(String[] args) {
        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.addTo60();
            System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);

        }, "AAA").start();

        while (myData.number == 0) {
            //main线程持有共享数据的拷贝,一直为0
        }
        System.out.println(Thread.currentThread().getName() + "\t mission is over. main get number value: " + myData.number);

    }
}

class MyData {
    int number = 0;

    public void addTo60() {
        this.number = 60;
    }
}
//结果
AAA	 come in
AAA	 update number value: 60

在这里插入图片描述

结果:程序一直在运行,主线程main检测到的number一直是0,证明线程AAA虽然改变了number 的值,但是与main线程之间没有实现可见性,主线程的number还是0.

使用volatile之后

package com.lty;

import java.util.concurrent.TimeUnit;

public class VolatileTest01 {
    public static void main(String[] args) {
        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.addTo60();
            System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);

        }, "AAA").start();

        while (myData.number == 0) {
            //main线程持有共享数据的拷贝,一直为0
        }
        System.out.println(Thread.currentThread().getName() + "\t mission is over. main get number value: " + myData.number);
    }
}

class MyData {
    volatile int number = 0;//使用volatile
    //int number = 0;
    public void addTo60() {
        this.number = 60;
    }
}
//结果
AAA	 come in
AAA	 update number value: 60
main	 mission is over. main get number value: 60

结果:mian线程的number值是60,循环结束

2)volatile不保证原子性

什么是原子性?
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。及时在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。
案例

package com.lty;

import java.util.concurrent.TimeUnit;

public class VolatileTest01 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myData.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t int type finally number value: "+myData.number); //结果并不是:20000
    }

    //volatile可以保证原子性,及时通知其他线程,主物理内存中值已经被修改过
    private static void seeOkByVilatile() {
        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.addTo60();
            System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);

        }, "AAA").start();

        while (myData.number == 0) {
            //main线程持有共享数据的拷贝,一直为0
        }
        System.out.println(Thread.currentThread().getName() + "\t mission is over. main get number value: " + myData.number);
    }
}

class MyData {
    //volatile int number = 0;
    int number = 0;
    public void addTo60() {
        this.number = 60;
    }
    public void addPlusPlus(){
        this.number++;
    }
}

结果并不是:20000

为什么不保证原子性?

这是因为,比如一条number++的操作,会形成3条指令。

getfield        //读
iconst_1	//++常量1
iadd		//加操作
putfield	//写操作

假设有3个线程,分别执行number++,都先从主内存中拿到最开始的值,number=0,然后三个线程分别进行操作。假设线程0执行完毕,number=1,刚要写回主内存时可能被挂起。但是此时线程1、2已经拿到了number=0,并运行完将结果写回主内存之后,线程0才继续获得时间片,将之前算的结果写入主内存,所以结果就是写覆盖。这样就会造成数据丢失。

解决办法:

1)加synchronized锁。(这种方法不推荐,有杀鸡用牛刀的感觉)

2)使用java.util.concurrent.AtomicInteger

package com.lty;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class VolatileTest01 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myData.addPlusPlus();
                    myData.addMyAtomic();
                }
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t int type finally number value: "+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t AtomicInteger type finally number value: "+myData.atomicInteger);
    }
}

class MyData {
    //volatile int number = 0;
    int number = 0;
    public void addTo60() {
        this.number = 60;
    }
    public  void addPlusPlus(){
        this.number++;
    }
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addMyAtomic(){
        atomicInteger.getAndIncrement();//这个方法是使atomicInteger自加1.
    }
}
//结果 :可见,由于`volatile`不能保证原子性,出现了线程重复写的问题,最终结果比20000小。而`AtomicInteger`可以保证原子性。
main	 int type finally number value: 19496
main	 AtomicInteger type finally number value: 20000

3)volatile的有序性,禁止指令重排

volatile可以保证有序性,也就是防止指令重排序。所谓指令重排序,就是出于优化考虑,CPU执行指令的顺序跟程序员自己编写的顺序不一致。

就好比一份试卷,题号是老师规定的,是程序员规定的,但是考生(CPU)可以先做选择,也可以先做填空。

int x = 11; //语句1
int y = 12; //语句2
x = x + 5;  //语句3
y = x * x;  //语句4

以上例子,可能出现的执行顺序有1234、2134、1342,这三个都没有问题,最终结果都是x = 16,y=256。但是如果是4开头,就有问题了,y=0。这个时候就不需要指令重排序。

volatile底层是用CPU的内存屏障(Memory Barrier)指令来实现的,有两个作用,一个是保证特定操作的顺序性,二是保证变量的可见性。在指令之间插入一条Memory Barrier指令,告诉编译器和CPU,在Memory Barrier指令之间的指令不能被重排序。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值