You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

面试题:基于Semaphore实现双线程同步(递增与显示线程)

面试题:线程同步(T1递增/T2仅更新时显示)的上下文切换处理方案

嘿,这个问题我当年面后端开发岗的时候也被面试官追问过,刚好可以给你梳理清楚前因后果和解决方案!

问题背景

原题需求:同步两个线程:线程T1负责递增数值x(初始值x=5),线程T2仅在x被更新为新值时显示该数值(比如x从5→6时显示6一次,x→7时显示7一次,绝对不能重复显示同一个值)。我最初给出了用Semaphore的实现方案,面试官紧接着追问:如果发生线程上下文切换,该如何处理?

核心问题分析

首先得明确:用Semaphore做基础同步时,上下文切换可能引发两个典型的竞态问题:

  • 问题1:T1在更新x和释放Semaphore之间被切走,导致T2被唤醒时读取的还是旧的x值
  • 问题2:T2在显示完x后,还没准备好等待下一次通知时被切走,T1多次释放Semaphore后,T2可能一次性读取多个更新值,破坏“仅显示一次新值”的要求

针对上下文切换的修复方案(基于Semaphore的优化)

解决的核心思路只有一个:把「数值更新」和「同步信号操作」绑定成不可拆分的原子操作,同时确保T2的显示逻辑和信号量获取严格对应,彻底避免上下文切换带来的脏读或重复唤醒。

方案1:锁+Semaphore组合(最稳妥的常规实现)

用一个可重入锁(比如Java的ReentrantLock)来保护x的读写,确保T1更新x和释放Semaphore的操作是原子的;同时T2在获取Semaphore后,必须持有锁才能读取x,避免读取到未完成更新的脏值。

给个Java代码示例(其他语言可以类比实现):

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSyncDemo {
    private int x = 5;
    // 初始时T2没有可获取的许可,因为x还未更新
    private final Semaphore updateSemaphore = new Semaphore(0);
    private final ReentrantLock valueLock = new ReentrantLock();

    // 线程T1:负责递增x
    class IncrementThread extends Thread {
        @Override
        public void run() {
            try {
                // 模拟5次递增操作
                for (int i = 0; i < 5; i++) {
                    valueLock.lock();
                    try {
                        x++; // 原子更新x的值
                        updateSemaphore.release(); // 释放许可,通知T2有新值
                    } finally {
                        valueLock.unlock(); // 无论如何都要释放锁
                    }
                    // 故意加休眠,模拟上下文切换的触发场景
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                // 正确处理中断
                Thread.currentThread().interrupt();
            }
        }
    }

    // 线程T2:仅在x更新时显示新值
    class DisplayThread extends Thread {
        @Override
        public void run() {
            try {
                // 对应T1的5次递增
                for (int i = 0; i < 5; i++) {
                    updateSemaphore.acquire(); // 等待T1的更新通知
                    valueLock.lock();
                    try {
                        System.out.println("更新后的x值:" + x);
                    } finally {
                        valueLock.unlock();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        ThreadSyncDemo demo = new ThreadSyncDemo();
        // 先启动T2,确保它先进入等待状态
        new demo.DisplayThread().start();
        new demo.IncrementThread().start();
    }
}

为什么这个方案能应对上下文切换?

  • 锁的原子性保障:就算T1在更新x后刚要释放Semaphore时被上下文切换切走,其他线程(包括T2)也无法读取到x,因为锁还没释放,彻底避免了脏读
  • Semaphore的严格计数:Semaphore的许可数严格对应T1的递增次数,就算T2被切走,许可数只会累计正确的次数,T2每次获取一个许可就对应一次新值显示,不会出现重复显示

方案2:用Condition替代Semaphore(更贴合场景的实现)

如果用Java的话,其实用Lock+Condition比Semaphore更贴合“等待更新通知”的场景,本质也是通过原子性操作解决上下文切换问题:

// 替换Semaphore为Condition
private final Condition updatedCondition = valueLock.newCondition();

// T1的run方法中,更新x后替换为:
updatedCondition.signal();

// T2的run方法中替换为:
valueLock.lock();
try {
    updatedCondition.await(); // 等待T1的更新通知
    System.out.println("更新后的x值:" + x);
} finally {
    valueLock.unlock();
}

这个方案的好处是更直观,signal()只会唤醒一个等待的线程(也就是T2),避免Semaphore可能出现的许可累计问题,同样能完美处理上下文切换带来的竞态。

面试官追问的深层意图

其实面试官问上下文切换的问题,本质是在考察你对同步操作的原子性和竞态条件的理解,而不是单纯的Semaphore用法——你需要证明你知道:同步逻辑不能拆分成多个独立步骤,否则上下文切换会破坏整个同步逻辑的正确性。


内容的提问来源于stack exchange,提问作者arkham knight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:21:48