volatile关键字
核心功能
是一种轻量级的同步机制,仅作用于变量(修饰成员变量或静态变量)。
核心功能是保证变量的可见性(一个线程修改后,其他线程能立即看到最新值)和禁止指令重排序(避免多线程下的执行顺序混乱),但不保证原子性。
适用场景
适合修饰状态标记变量(可见性)
(如控制线程启停的running标志)
/**
* Redis消息队列监听器
* 该类负责监听Redis队列中的消息,并将消息分发给相应的消费者进行处理
*
* @author keepa
*/
@Slf4j
@Component
public class RedisMessageQueueListener {
/**
* 队列轮询超时时间(秒)
*/
private static final long QUEUE_POLL_TIMEOUT_SECONDS = 5L;
/**
* 线程关闭等待超时时间(秒)
*/
private static final long THREAD_SHUTDOWN_TIMEOUT_SECONDS = 5L;
private final RedisQueueService redisQueueService;
private final EmailNotifyConsumer emailNotifyConsumer;
/**
* 消息监听任务执行器
* 用于执行消息监听任务的线程池
*/
private ExecutorService executorService;
/**
* 监听器运行状态标志
* 使用volatile关键字确保多线程环境下的可见性
*/
private volatile boolean running = true;
public RedisMessageQueueListener(RedisQueueService redisQueueService, EmailNotifyConsumer emailNotifyConsumer) {
this.redisQueueService = redisQueueService;
this.emailNotifyConsumer = emailNotifyConsumer;
}
/**
* 启动消息监听任务
* 该方法在Spring容器初始化该Bean时自动调用
*/
@PostConstruct
public void startMessageListener() {
this.executorService = Executors.newSingleThreadExecutor();
this.executorService.submit(this::listenForMessages);
log.info("Redis消息队列监听器已启动");
}
/**
* 停止消息监听任务
* 该方法在Spring容器销毁该Bean时自动调用
*/
@PreDestroy
public void stopMessageListener() {
log.info("正在停止Redis消息队列监听器...");
this.running = false;
if (this.executorService != null) {
try {
// 给线程一些时间来完成当前任务
if (!this.executorService.awaitTermination(THREAD_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
log.warn("Redis消息监听器未能在{}秒内正常关闭,强制关闭", THREAD_SHUTDOWN_TIMEOUT_SECONDS);
this.executorService.shutdownNow();
}
} catch (InterruptedException e) {
log.warn("等待Redis消息监听器关闭时被中断", e);
this.executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
log.info("Redis消息队列监听器已停止");
}
/**
* 监听消息队列并处理消息
* 该方法在独立线程中运行,持续轮询Redis队列中的消息
*/
private void listenForMessages() {
log.info("开始监听Redis队列消息: {}", this.redisQueueService.getApprovalNotifyQueueName());
while (this.running) {
try {
// 从审批通知队列获取消息,超时QUEUE_POLL_TIMEOUT_SECONDS秒
Object message = this.redisQueueService.pullMessage(
this.redisQueueService.getApprovalNotifyQueueName(),
QUEUE_POLL_TIMEOUT_SECONDS,
TimeUnit.SECONDS);
if (message != null) {
// 处理消息
this.emailNotifyConsumer.onMessage(message);
}
} catch (Exception e) {
// 在关闭过程中忽略连接关闭异常
if (this.running) {
log.error("处理队列消息时发生异常", e);
} else {
log.debug("应用关闭过程中发生异常,可能是由于Redis连接已关闭", e);
}
}
}
log.info("Redis消息监听线程已退出");
}
}
在这段RedisMessageQueueListener代码中,running变量是控制监听线程生命周期的标志:
- 监听线程(由
executorService创建的线程)在listenForMessages()方法中,通过while (this.running)循环持续轮询 Redis 队列。 - 主线程(或管理线程) 在
stopMessageListener()方法中,通过this.running = false修改标志,通知监听线程停止运行。
如果running不使用volatile修饰,可能会出现以下问题:
- 由于 JVM 的 “工作内存” 机制,监听线程可能会将
running的值缓存到自己的工作内存中,而非每次从主内存读取。 - 当主线程修改
running为false时,监听线程可能无法及时感知到这个变化(仍读取缓存的旧值true),导致循环无法退出,监听线程无法正常停止。
而使用volatile修饰后:
- 每次监听线程读取
running时,都会强制从主内存获取最新值。 - 每次主线程修改
running后,都会立即同步到主内存,确保监听线程能及时看到false的新值,从而退出循环,正确终止线程。
本代码中volatile的作用是保证running变量在多线程环境下的可见性,确保 “停止监听” 的指令能被监听线程正确感知,避免出现线程无法终止、资源无法释放的问题。这是volatile在 “多线程状态控制” 场景中的典型应用。
单例模式的双重检查锁(禁止指令重排)
双重检查锁(Double-Checked Locking)是单例模式中一种高效的线程安全实现方式,既能保证懒加载(延迟初始化),又能减少同步开销。其核心是通过两次检查实例是否存在,配合volatile关键字防止指令重排序,实现高性能的线程安全单例。
public class Singleton {
// 用volatile修饰实例变量,防止指令重排序
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;
}
}
关键细节解析:
volatile关键字的必要性:
instance = new Singleton()实际可分解为三步操作:
① 分配内存空间;② 初始化对象;③ 将引用指向内存空间。
若不加volatile,JVM 可能对②和③进行指令重排序(先执行③再执行②),导致某个线程获取到 “未完全初始化” 的实例(引用不为 null 但对象未初始化完成)。volatile可禁止这种重排序,确保实例完全初始化后才被其他线程可见。- 双重检查的作用:
- 第一次检查(同步块外):避免每次调用
getInstance()都进入同步块,减少性能开销(多数情况下实例已存在,直接返回)。 - 第二次检查(同步块内):防止多个线程同时通过第一次检查后,在同步块中重复创建实例(例如线程 A 进入同步块创建实例时,线程 B 等待,A 创建完成后 B 进入同步块,此时第二次检查会发现实例已存在,避免重复创建)。
- 第一次检查(同步块外):避免每次调用
- 私有构造方法:
确保外部无法通过new关键字创建实例,保证单例的唯一性。
这种实现方式在多线程环境下既安全又高效,是懒加载单例模式的推荐实现之一。

被折叠的 条评论
为什么被折叠?



