MST

星途 面试题库

面试题:Java多线程资源竞争场景下的原子性、可见性与有序性分析及解决方案

在Java多线程处理资源竞争时,原子性、可见性与有序性是非常关键的概念。请深入分析在不同资源竞争场景下,这三个特性会遇到哪些问题,以及如何通过Java内存模型(JMM)、volatile关键字、Atomic类等机制来分别解决这些问题,同时说明这些机制的底层实现原理。
20.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

原子性问题及解决

  1. 问题场景
    • 当多个线程同时访问和修改共享变量时,可能会出现操作不完整的情况。例如,i++操作在底层实际由读取、加1、写入三个步骤组成,多线程下可能一个线程读取值后,另一个线程也读取值,然后各自加1并写入,导致结果并非预期的连续递增。
  2. 解决方案
    • 使用Atomic:如AtomicInteger,它提供了原子操作方法,如incrementAndGet()实现原子性自增。
    • 原理Atomic类利用了CPU的原子指令(如CAS - Compare and Swap)。以AtomicIntegerincrementAndGet为例,它通过Unsafe类的compareAndSwapInt方法,将内存中的值与预期值比较,如果相等则更新为新值,否则重试,保证操作的原子性。

可见性问题及解决

  1. 问题场景
    • 每个线程有自己的工作内存,线程对共享变量的修改先在工作内存中进行,之后才会同步回主内存。当一个线程修改了共享变量但未及时同步回主内存,其他线程在自己工作内存中读取的仍是旧值,导致可见性问题。例如,一个线程修改了标志位变量,另一个线程却未感知到这个变化。
  2. 解决方案
    • 使用volatile关键字:被volatile修饰的变量,对其修改会立即同步到主内存,并且每次读取都会从主内存刷新,保证了不同线程对该变量的可见性。
    • JMM机制:JMM定义了主内存和工作内存之间的交互协议,规定了何时将工作内存的数据同步回主内存以及何时从主内存读取数据。对于volatile变量,JMM有特殊的内存屏障指令,在写操作后插入写屏障,在读操作前插入读屏障,确保可见性。
  3. 原理
    • volatile原理volatile通过内存屏障实现可见性。写屏障会确保在该屏障之前的所有写操作都同步到主内存;读屏障会确保在该屏障之后的所有读操作都从主内存读取最新值。

有序性问题及解决

  1. 问题场景
    • 为了提高性能,编译器和处理器会对指令进行重排序。在单线程环境下,重排序不会影响最终结果,但在多线程环境下,可能导致程序执行结果不符合预期。例如,在初始化对象时,可能先分配内存,然后初始化对象,最后将对象引用赋值给变量,但重排序后可能先赋值引用,此时其他线程获取到的是未初始化完成的对象。
  2. 解决方案
    • 使用volatile关键字volatile不仅保证可见性,也能保证一定的有序性。因为volatile写操作之前的操作不会被重排序到volatile写之后,volatile读操作之后的操作不会被重排序到volatile读之前。
    • 使用synchronized关键字synchronized块在进入时会从主内存刷新变量值,退出时会将变量值同步回主内存,同时也禁止了块内代码的指令重排序,保证了有序性。
  3. 原理
    • volatile有序性原理:通过内存屏障禁止特定类型的重排序。例如,写屏障禁止之后的普通写操作重排序到屏障之前,读屏障禁止之前的普通读操作重排序到屏障之后。
    • synchronized有序性原理:通过monitorenter和monitorexit指令实现。monitorenter指令会使当前线程获取锁,进入同步块时刷新工作内存变量;monitorexit指令会释放锁,退出同步块时同步工作内存变量到主内存,同时禁止重排序。