java多线程-volatile的使用

递归星轨
• 阅读 1184

volatile关键字

主要作用:

​ 1.保证数据之间的可见性。

​ 2.禁止指令重排序。

1.可见性

2.做个小的测试

public class VolatileTest implements Runnable {
    //当为false时线程结束
    private static /*volatile*/ boolean flag = true;
    private static int value = 100;
    @Override
    public void run() {
        // TODO Auto-generated method stub

        while(flag) {
            value++;
            //System.out.println(value);//可以取消注释试一试
        }
        System.out.println(Thread.currentThread().getName()+"结束");
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new VolatileTest() ).start();
        Thread.sleep(1000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                flag = false;
                System.out.println(Thread.currentThread().getName()+"结束");
            }
        }).start();
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"结束");
        System.out.println("flag="+flag);
    }

}

结果:

Thread-1结束
main结束
flag=false

我们可以发现,第二个线程将flag改成false,但是第一个线程并没有停止运行。

1.多线程内存模型

java多线程-volatile的使用

3.为什么?

从第一幅图我们可以看出,各一个线程都有一个工作内存,线程运行时,他会从主内存读取数据到工作内存,然后在使用工作内存,执行完在save到工作内存.但是其他线程感知不到主内存的变化,不知道主内存的flag变成了false,所以没有更新自己的工作空间中的flag(因为没有操作让他去主内存读取数据),导致flag为true,所以循环无法终止.

4.volatile的作用:强制让其读取主内存,而不是工作空间,多个线程使用的为同一个空间,就保证了可见性.

5.volatile保证了可见性但是却没有保证原子性.需要其他操作来实现。

如果将flag的volatile添加上。

结果:

Thread-1结束
Thread-0结束
main结束
flag=false

2.禁止指令重排序(有序性)

volatile禁止jvm和处理器对volatile修饰的指令进行重排序,但是修饰符前和后的指令没有明确的规定。

何为重排序:

​ 在单线程下:jvm为了提高执行的效率,会对我们的代码进行优化,对我们的指令进行位置的更换,但是更换有个前提,就是在单线程下逻辑不变,比如:

int a = 1;
int b = 2;
int c = a + b;
//更换为
int b = 2;
int a = 1;
int c = a + b;

这种更换不会改变结果(单线程下)。

同时对于cpu来说,为了满足效率问题,也会对我们的指令进行重排序,提高cpu流水线的效率。

重排序规则:

int a = 1;
int b = 2;
volatile int c = 3;
int d = 4;
int f = 6;

volatile可以禁止重排序,但是只针对修饰的命令,对于上面的程序,a,b没有修饰,所以,a,b可以重排序,同时d,f也可以,但是ab和df是不会进行重排序的,因为volatile生成内存屏障

(1)volatile写操作前面插入一个StoreStore屏障。确保在进行volatile写之前前面的所有普通的写操作都已经刷新到了内存。

(2)volatile写操作后面插入一个StoreLoad屏障。避免volatile写操作与后面可能存在的volatile读写操作发生重排序。

(3)volatile读操作后面插入一个LoadLoad屏障。避免volatile读操作和后面普通的读操作进行重排序。

(4)volatile读操作后面插入一个LoadStore屏障。避免volatile读操作和后面普通的写操作进行重排序。

简单来说,volatile修饰的前面的指令不会和后面的指令进行重排序,同时运行volatile修饰时,前面的代码全部执行完毕。

举个重排序的例子:

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

    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        long start = System.currentTimeMillis();
        while(true){
            count++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(() -> {
                a = 1;
                x = b;
            });
            Thread other = new Thread(() -> {
                b = 1;
                y = a;
            });
            one.start();other.start();
            one.join();other.join();
            if (x == 0 && y ==0){
                long end = System.currentTimeMillis();
                System.out.println("程序运行次数:"+count);
                System.out.println("程序耗时:"+(end-start));
                break;
            }
        }
    }
}

加入,我们认为程序是一行一行执行的,即顺序不会发生改变。

情况1(one)情况1(other)情况2(one)情况2(other)情况3(one)情况3(other)
a = 1 a = 1 a = 1
x = b b = 1 b = 1
b = 1x = b y = a
y = a y = ax = b
结果a=1
x=0
b=1
y=1
a=1
x=1
b=1
y=1
a=1
x=1
b=1
y=1
情况4(one)情况4(other)情况5(one)情况5(other)情况6(one)情况6(other)
b = 1 b = 1 b = 1
a = 1 a = 1 y = a
x = b y = aa = 1
y = ax = b x = b
结果a=1
x=1
b=1
y=1
a=1
x=1
b=1
y=1
a=1
x=1
b=1
y=0

按照上述排序,可以看出永远是不会出现x =0 和y = 0同时存在的情况出现,那么这个程序就没有结果。

但是运行程序:

程序运行次数:5130
程序耗时:2453

程序成功退出,表示出现了xy同时为零的情况。这表示某个线程的指令没有按照顺序执行,顺序被打乱了。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
4年前
volatile实现可见性但不保证原子性
   volatile关键字:能够保证volatile变量的可见性不能保证volatile变量复合操作的原子性         volatile如何实现内存可见性:        深入来说:通过加入内存屏障和禁止重排序优化来实现的。对volatile变量执行写操作时,会在写操作后加入一条store屏
Wesley13 Wesley13
4年前
Volatile关键字
Volatile关键字①volatile的两个特点1保证线程(CPU)之间的可见性;(也就是保证数据一致性)简单解释一下:一个线程将一个值的数值改变时,另一个使用该数值的线程能看到这种改变;2禁止指令重排序(禁止乱序执行);这个和单例
Stella981 Stella981
4年前
Linux查看GPU信息和使用情况
1、Linux查看显卡信息:lspci|grepivga2、使用nvidiaGPU可以:lspci|grepinvidia!(https://oscimg.oschina.net/oscnet/36e7c7382fa9fe49068e7e5f8825bc67a17.png)前边的序号"00:0f.0"是显卡的代
Wesley13 Wesley13
4年前
Java多线程之volatile详解
目录:什么是volatile?JMM内存模型之可见性volatile三大特性之一:保证可见性volatile三大特性之二:不保证原子性volatile三大特性之三:禁止指令重排小结1.什么是volatile?答:volatile是java虚拟机提供的轻量级的同步机制(
Stella981 Stella981
4年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
4年前
Java 多线程:volatile关键字
概念volatile也是多线程的解决方案之一。\\volatile能够保证可见性,但是不能保证原子性。\\它只能作用于变量,不能作用于方法。当一个变量被声明为volatile的时候,任何对该变量的读写都会绕过高速缓存,直接读取主内存的变量的值。如何理解直接读写主内存的值:回到多线程生成的原因(Java内存模型与
递归星轨
递归星轨
Lv1
看破生死,世间的一切都没有什么好争的。
文章
8
粉丝
0
获赞
0