【Java】java多线程-volatile的使用

超级大咸鱼发布于 今天 04:46

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】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 = 1a = 1a = 1
x = bb = 1b = 1
b = 1x = by = a
y = ay = 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 = 1b = 1b = 1
a = 1a = 1y = a
x = by = aa = 1
y = ax = bx = 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同时为零的情况。这表示某个线程的指令没有按照顺序执行,顺序被打乱了。

以上是 【Java】java多线程-volatile的使用 的全部内容, 来源链接: utcz.com/a/111031.html

回到顶部