大家好,我是鸭鸭!
大家对商战的印象,在见过当当抢公章,开水浇发财树,评论区阴阳怪气等一系列真实商战后,已经对各大公司的“有病”行为有了一定的心理准备。
没想到,今天鸭鸭又看到了新的商战战报:顺丰员工破坏京东揽收点后,获得1.2万元奖励。
商战,依然如此朴实无华。
鸭鸭查了一下新闻,据说这是因为,近期正值大闸蟹旺季,很多快递公司都在苏州协新村网点开设了大闸蟹揽收业务。京东做了一块广告牌,隔壁顺丰觉得影响自己的生意,两边爆发了言语冲突。当天夜里,京东的广告牌和网点就被破坏了。
京东报警之后,4名嫌疑人都被警察叔叔带走治安拘留。
但京东的工作人员没想到,顺丰竟然还做了海报,并发了1.2万现金奖励这4位员工。当然,顺丰的工作人员表示,我们这个奖励,是其他项目的,和这件事没有关系。
但大家这不是懂得都懂。
要鸭鸭说,电视剧和小说,还是太保守了一些。真实的商战,干就完了。
……
马上周末了,来面试鸭摸个鱼,看看今天的面试题。
什么是 Java 中的指令重排?
回答重点
指令重排是 Java 编译器和处理器为了优化性能,在保证单线程程序语义不变的情况下,对指令执行顺序进行调整的过程。在多线程环境下,指令重排可能导致线程之间的操作出现不同步或不可见的现象,因此 Java 提供了内存模型(JMM)和相关机制(如 volatile
和 synchronized
)来限制这种行为,确保并发操作的正确性。
主要原因:
- 编译器优化:编译器会在不影响单线程程序语义的情况下重排序代码,以提升执行效率。
- 处理器优化:现代处理器会进行指令流水线优化,允许多条指令并行执行或重排序。
重排序的影响:
- 单线程情况下不会影响程序执行结果。
- 多线程情况下,指令重排可能导致线程之间的数据不一致问题,影响并发的正确性。
扩展知识
指令重排的三种类型
- 编译器重排:编译器在生成字节码时,根据优化策略调整代码的顺序,前提是不会改变程序的单线程语义。
- CPU 重排:处理器执行指令时,可能会对指令顺序进行调整,以充分利用 CPU 资源,例如指令流水线和多核并行执行。
- 内存系统重排:不同线程访问共享内存时,内存系统可能会对内存操作顺序进行调整。
单例双重检查锁定的指令重排问题
单例模式中的“双重检查锁定”就是为了避免指令重排的问题。在初始化单例对象时,由于编译器或 CPU 的指令重排,可能会导致另一个线程读取到未初始化完成的对象。这种情况可以通过使用 volatile
关键字来避免。
示例代码:
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton(); // 可能会发生指令重排}}}return instance;}
}
在上述例子中,instance = new Singleton();
实际上分为三步操作:
- 分配内存空间。
- 初始化对象。
- 将对象指向内存地址。
如果没有 volatile
关键字,编译器或处理器可能会重排步骤 2 和步骤 3,这就会导致另一个线程可能读取到一个尚未初始化完成的对象。
如何避免指令重排导致的并发问题
-
使用
volatile
关键字来确保关键变量的读写操作不被重排。 -
使用
synchronized
来保证代码块的顺序执行。 -
遵循
happens-before
原则来确保多线程环境下的可见性和有序性。