[Java] volatile关键字

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的值缓存到自己的工作内存中,而非每次从主内存读取。
  • 当主线程修改runningfalse时,监听线程可能无法及时感知到这个变化(仍读取缓存的旧值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;
    }
}

关键细节解析:

  1. volatile关键字的必要性
    instance = new Singleton() 实际可分解为三步操作:
    ① 分配内存空间;② 初始化对象;③ 将引用指向内存空间。
    若不加volatile,JVM 可能对②和③进行指令重排序(先执行③再执行②),导致某个线程获取到 “未完全初始化” 的实例(引用不为 null 但对象未初始化完成)。volatile可禁止这种重排序,确保实例完全初始化后才被其他线程可见。
  2. 双重检查的作用
    • 第一次检查(同步块外):避免每次调用getInstance()都进入同步块,减少性能开销(多数情况下实例已存在,直接返回)。
    • 第二次检查(同步块内):防止多个线程同时通过第一次检查后,在同步块中重复创建实例(例如线程 A 进入同步块创建实例时,线程 B 等待,A 创建完成后 B 进入同步块,此时第二次检查会发现实例已存在,避免重复创建)。
  3. 私有构造方法
    确保外部无法通过new关键字创建实例,保证单例的唯一性。

这种实现方式在多线程环境下既安全又高效,是懒加载单例模式的推荐实现之一。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值