前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >volatile指令重排[通俗易懂]

volatile指令重排[通俗易懂]

作者头像
全栈程序员站长
发布2022-11-07 16:58:41
6100
发布2022-11-07 16:58:41
举报
文章被收录于专栏:全栈程序员必看

什么时候会发生指令重排?

先来一个测试指令重排现象,下面这段代码会发生指令重排,也就是JVM优化了执行顺序。

代码语言:javascript
复制
/** * 指令重排测试 */
public class CommandDisorder { 

// 当使用volatile关键词修饰变量时,则不会出现指令重排现象
private static /*volatile*/ int a = 0, b = 0, c = 0, d = 0;
/** * 测试方式:一次开启两个线程,同时修改变量 */
public static void main(String[] args) throws InterruptedException { 

int i = 0;
while (true) { 

i++;
a = b = c = d = 0;
Thread t1 = new Thread(() -> { 

a = 1;
c = b; // 指令重排,会先执行这行代码,导致c = 0, d = 0
});
Thread t2 = new Thread(() -> { 

b = 1;
d = a; // 指令重排,会先执行这行代码,导致c = 0, d = 0
});
t1.start();
t2.start();
t1.join();
t2.join();
if (c == 0 && d == 0) { 

System.err.println(String.format("第%s次出现指令重排", i));
break;
} else { 

System.out.println(i);
}
}
}
}

指令重排,异常出现了:

img
img

什么是指令重排?

为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序,JMM内部会有指令重排,并且会有af-if-serial和happen-before的理念来保证指令的正确性。

  • af-if-serial:不管怎么重排序,单线程下的执行结果不能被改变;
  • 先行发生原则(happen-before):先行发生原则有很多,其中程序次序原则,在一个线程内,按照程序书写的顺序执行,书写在前面的操作先行发生于书写在后面的操作,准确地讲是控制流顺序而不是代码顺序。

真实的业务中如何避免指令重排?

在真实业务场景中,预测到可能有多线程访问同一个变量时,建议加上volatile关键词,保证变量在线程间的可见性。

举一个简单的例子,单例模式

代码语言:javascript
复制
public class Singleton { 

// 为了避免指令重排,这里需要加上volatile关键词
private static /*volatile*/ Singleton singleton = null;
/** * double check lock(DCL) */
public static Singleton getInstance() { 

if (singleton == null) { 

synchronized (Singleton.class) { 

if (singleton == null) { 

// new 一个对象的过程,有三个步骤
// 1.内存分配
// 2.初始化
// 3.返回对象引用
// 由于JVM指令重排优化,可能会使得2、3两步顺序发生变化,说明这里不是一个原子性操作
singleton = new Singleton();
}
}
}
return singleton;
}
private static void removeInstance() { 

singleton = null;
}
private final String field = "init";
public static void main(String[] args) throws InterruptedException, ExecutionException { 

ExecutorService threadPool = Executors.newCachedThreadPool();
long start = System.currentTimeMillis();
for (int j = 0; j < 3000_000; j++) { 

execute(threadPool);
Singleton.removeInstance();
}
System.out.println("正常结束");
System.out.println("执行耗时:" + (System.currentTimeMillis() - start) + " ms");
System.exit(0);
}
/** * 使用500个线程同时去获取实例 */
private static final int THREAD_COUNT = 500;
private static void execute(ExecutorService threadPool) throws InterruptedException, ExecutionException { 

CountDownLatch downLatch = new CountDownLatch(THREAD_COUNT);
List<Callable<Singleton>> list = new ArrayList<>();
for (int i = 0; i < THREAD_COUNT; i++) { 

list.add(() -> { 

downLatch.countDown();
Singleton instance = Singleton.getInstance();
if (instance.field == null) { 

throw new RuntimeException("获取到未实例化的对象");
}
return instance;
});
}
List<Future<Singleton>> futures = threadPool.invokeAll(list);
Set<Singleton> set = new HashSet<>();
for (Future<Singleton> future : futures) { 

set.add(future.get());
}
if (set.size() > 1) { 

System.out.println("产生多实例!");
throw new RuntimeException("产生多实例!");
}
}
}

当没有采用DCL时,可能会产生多实例。采用了DCL而没有使用volatile关键词,则可能出现:获取到未实例化的对象,原理见第一个示例。

看似一个简单的单例,内部却隐含了不少有意思的内容。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/183443.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月10日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么时候会发生指令重排?
  • 什么是指令重排?
  • 真实的业务中如何避免指令重排?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档