java中什么是指令重排序_【Java】聊聊多线程中的指令重排序

首页

专栏

java

文章详情

0

聊聊多线程中的指令重排序

109909.html小强大人发布于 1 月 28 日

什么是指令重排序?

Java内存模型允许编译器和处理器对指令进行重排序来提升运行性能,当然只会对那些不存在数据依赖的指令间进行重排序,不然的话会得到错误执行结果。然而,这种保证只对单线程有效,多线程环境下并不能保证这点,这就导致了多线程环境下,重排序后运行的结果并不是我们所预期的那样,这就是指令重排序。

下面举个栗子具体说明:

先来看个正常不会出问题的栗子:

public int add() {

int x = 1; //(1)

int y = 2; //(2)

int z = x + y; //(3)

return z;

}

上面的代码中,(1)和(2)没有依赖关系,它们之间可以重排序,(3)依赖了(1)和(2),所以(3)不会被重排序到(1)或(2)之前,这个栗子中,(1)(2)即使发生了重排序,也不会影响程序运行结果。

再来看个可能会出现问题的栗子:

public class InstructionReorderDemo {

static int num = 0;

static boolean ready = false;

public static void main(String[] args) throws InterruptedException {

Thread readThread = new Thread(() -> {

if (ready) { //(1)

System.out.println(num + num); //(2)

}

});

Thread writeThread = new Thread(() -> {

num = 2; //(3)

ready = true; //(4)

});

readThread.start();

writeThread.start();

readThread.join();

writeThread.join();

System.out.println("main thread exit.");

}

}

这个栗子中,由于(1)(2)(3)(4)之间都没有数据依赖,可能会有同学有疑问,(1)不是依赖了(4)吗,(2)不是依赖了(3)吗?别忘了,这4条指令放在单线程下才能保证这点,就是说下面的代码才会产生上述依赖:

int num;

boolean ready;

public void calculate {

num = 2;

ready = true;

if (ready) {

System.out.println(num + num);

}

}

这里是多个线程分别访问共享变量ready和num,且它们都没有用volatile修饰,两个线程并发运行,编译器/处理器可能会对(3)(4)进行重排序,因为它们之间没有数据依赖。但这时,如果按如下时间线运行,就会出现运行结果是0,而不是4。

readThread

writeThread

ready = true //(4)

if(ready) //(1)

num + num //(2)

num = 2 //(3)

如何避免指令重排序?

方法很简单,把上述程序中的ready变量用volatile修饰即可。volatile关键字不仅保证了内存可见性,还有内存屏障的作用,在JVM底层是用Lock前缀的指令实现的。它保证了在写volatile变量指令时,在它之前的指令不会被重排序到它之后,在这个栗子中就是num = 2不会在ready = true之后执行;在读volatile变量时,在它之后的指令不会被重排序到它之前,在这个栗子中就是num + num不会放在if (ready)之前执行。

参考资料:

《Java并发编程之美》

java

阅读 43发布于 1 月 28 日

赞收藏

分享

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

109909.html

小强大人

9声望

1粉丝

关注作者

0 条评论

得票时间

109909.html

提交评论

109909.html

小强大人

9声望

1粉丝

关注作者

宣传栏

目录

什么是指令重排序?

Java内存模型允许编译器和处理器对指令进行重排序来提升运行性能,当然只会对那些不存在数据依赖的指令间进行重排序,不然的话会得到错误执行结果。然而,这种保证只对单线程有效,多线程环境下并不能保证这点,这就导致了多线程环境下,重排序后运行的结果并不是我们所预期的那样,这就是指令重排序。

下面举个栗子具体说明:

先来看个正常不会出问题的栗子:

public int add() {

int x = 1; //(1)

int y = 2; //(2)

int z = x + y; //(3)

return z;

}

上面的代码中,(1)和(2)没有依赖关系,它们之间可以重排序,(3)依赖了(1)和(2),所以(3)不会被重排序到(1)或(2)之前,这个栗子中,(1)(2)即使发生了重排序,也不会影响程序运行结果。

再来看个可能会出现问题的栗子:

public class InstructionReorderDemo {

static int num = 0;

static boolean ready = false;

public static void main(String[] args) throws InterruptedException {

Thread readThread = new Thread(() -> {

if (ready) { //(1)

System.out.println(num + num); //(2)

}

});

Thread writeThread = new Thread(() -> {

num = 2; //(3)

ready = true; //(4)

});

readThread.start();

writeThread.start();

readThread.join();

writeThread.join();

System.out.println("main thread exit.");

}

}

这个栗子中,由于(1)(2)(3)(4)之间都没有数据依赖,可能会有同学有疑问,(1)不是依赖了(4)吗,(2)不是依赖了(3)吗?别忘了,这4条指令放在单线程下才能保证这点,就是说下面的代码才会产生上述依赖:

int num;

boolean ready;

public void calculate {

num = 2;

ready = true;

if (ready) {

System.out.println(num + num);

}

}

这里是多个线程分别访问共享变量ready和num,且它们都没有用volatile修饰,两个线程并发运行,编译器/处理器可能会对(3)(4)进行重排序,因为它们之间没有数据依赖。但这时,如果按如下时间线运行,就会出现运行结果是0,而不是4。

readThread

writeThread

ready = true //(4)

if(ready) //(1)

num + num //(2)

num = 2 //(3)

如何避免指令重排序?

方法很简单,把上述程序中的ready变量用volatile修饰即可。volatile关键字不仅保证了内存可见性,还有内存屏障的作用,在JVM底层是用Lock前缀的指令实现的。它保证了在写volatile变量指令时,在它之前的指令不会被重排序到它之后,在这个栗子中就是num = 2不会在ready = true之后执行;在读volatile变量时,在它之后的指令不会被重排序到它之前,在这个栗子中就是num + num不会放在if (ready)之前执行。

参考资料:

《Java并发编程之美》

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值