2026/9/11 21:20:56

多线程面试考点全解析:从C++11到Java/Python/Linux/QT

多线程面试考点全解析:从C++11到Java/Python/Linux/QT 1. 为什么多线程面试题年年考、还总考不完多线程自己敲代码的时候觉得没什么一到面试就露怯。我这些年面过不少人也帮朋友模拟过不少次面试发现一个规律凡是能把多线程讲清楚的候选人基础功底基本都不差。原因很直接——多线程不是一门语言里的孤立语法它牵扯操作系统调度、内存模型、锁的底层实现、并发数据结构、性能分析甚至还包括调试和线上问题排查。面试官问多线程问的其实是你对整个计算机体系的理解深度。很多初学者有个误区觉得多线程就是“开几个线程做事情”背几个面试题就完事了。但真正的面试场景里面试官会从一个很小的点切入比如“wait()和sleep()有什么区别”然后一路追问到“notify()和notifyAll()怎么选”“超时等待怎么处理”“虚假唤醒是什么”“中断标志位怎么复位”——这一串问题下来八成的候选人会卡在中间某个环节。这篇文章不是基础教程而是按我实际面试和辅导的经验把多线程备考里最容易被问住、也最能拉开分差的几个板块拆开讲清楚。其中会覆盖C11、Java、Python、Linux/POSIX和QT这几个高频场景因为不同语言的多线程实现虽然API风格不同但底层核心模型是相通的——你要会的不只是一个接口的用法而是同一套并发问题在不同语言里分别是怎么解、为什么这么解。无论你是准备校招、社招还是工作中被拉去临时顶一场技术面这篇文章都能帮你把多线程的备考框架立起来。文章里所有代码和结论都来自我实际跑过的验证和线上面试点评的真实反馈可以直接拿来用。另外一个很现实的原因现在业务系统稍微上点规模并发就是绕不开的话题。高并发接口、消息队列消费、定时任务调度、异步日志、连接池——这些生产环境里天天碰到的场景底层全都是多线程。所以面试官问多线程不只是在考你背没背过八股文更是在预估你入职以后能不能独立处理线上问题。这一关绕不开。2. 多线程面试的底层逻辑面试官到底在考什么2.1 五大核心维度从创建线程到内存可见性我总结过一套自己的多线程考点地图基本能把面试题归成五大类第一类是线程的创建与生命周期。不管是C11的std::thread、Java的Thread和Runnable、Python的threading模块还是Linux的pthread_create面试官都会让你说说线程从创建到销毁经历了哪些状态。这里是入门题但答得漂不漂亮能看出你有没有真正写过并发代码。比如Java里线程的六种状态NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED很多人能背出来但问到“sleep和wait分别让线程进入什么状态”就开始含糊了。第二类是竞态条件与临界区。这是并发编程的核心矛盾多个线程同时访问共享数据如何保证结果正确。几乎所有锁相关的面试题都是从这个维度延伸出来的。面试官会让你分析一段代码有没有问题、问题出在哪、怎么修。这里没有标准答案背必须真正理解“原子性”“可见性”“有序性”这三个词的含义。第三类是线程间协作与通信。包括条件变量、信号量、管道、消息队列、Future/Promise模型等。面试题常见的有“两个线程交替打印奇偶数”“生产者消费者模型”“三个线程按顺序打印ABC”。这类题表面考代码实际考的是你对等待、唤醒、阻塞、超时这些机制的理解深度。第四类是并发工具与线程池。这层考的是工程能力。Java的synchronized与ReentrantLock的区别、ConcurrentHashMap的分段锁设计、C11里std::atomic和std::mutex怎么选、Python里GIL到底锁的是什么、线程池的七个核心参数这些都是高频考点。第五类是死锁、活锁与性能调优。死锁是并发编程最经典的问题面试官几乎必问“死锁的四个必要条件”以及“如何避免死锁”。再往上走还会问你锁竞争导致的性能瓶颈怎么分析怎么用无锁编程、读写锁、减小锁粒度等手段优化。这五大维度其实和面试官考核的思路完全吻合先看你会不会用API层再看你懂不懂原理机制层最后看你有没有实战经验工程层。2.2 多语言横向对比这才是拉开差距的地方我面试别人的时候最喜欢问的一句话是“你平时主用哪个语言那别的语言的多线程你了解吗”这个问题能把纯背题的人和真正理解并发的人区分开。因为多线程的底层概念是通用的只是不同语言的封装形式不同。拿最简单的“线程间如何通知”举例。C11里用std::condition_variable配std::unique_lockJava里用Object.wait()/notify()或ConditionPython里用threading.ConditionLinux底层就是pthread_cond_wait和pthread_cond_signalQT里则是QWaitCondition加信号槽机制。语法都不同但本质都是“让线程在某个条件不满足时挂起等条件满足时被唤醒”。你要是理解了这一层学任何一门新语言的多线程都很快。反过来面试官也会通过横向对比来试探你的知识边界。比如问你“Java的synchronized锁的是对象还是代码块”“C11的std::async和Java的FutureTask有什么异同”“Python的多线程为什么在多核CPU上反而可能更慢”。这些问题没有标准答案但答得越深入越说明你平时不是简单调用API而是真的思考过底层实现。所以我给备考朋友的建议一直是不要只准备自己熟练语言的多线程把主语言周边的一两种对比也顺便准备一下。不需要精通只要能说清楚关键差异即可这在面试中很加分。3. C11多线程面试中的重头戏3.1 从std::thread到std::asyncC11线程的完整拼图C11可以说是C多线程的分水岭。在C11之前C的多线程基本靠操作系统APILinux下用pthreadWindows下用Windows API代码没法跨平台而且很容易写出资源泄漏。C11把线程库标准化之后情况好了很多但随之而来的是一大堆新的面试考点。std::thread基本用法是最基础的问题。你需要能现场写出一个简单示例并且能说清楚join()和detach()的区别。这里有个特别容易踩的坑如果既没join()也没detach()线程析构时会直接调用std::terminate导致程序崩溃。我面试时经常拿这个当陷阱题问候选人“这段代码有什么问题”很多人看了半天看不出来。#include iostream #include thread void worker(int id) { std::cout thread id running std::endl; } int main() { std::thread t1(worker, 1); // 如果这里什么也不写就return程序会直接终止 if (t1.joinable()) { t1.join(); } return 0; }**std::async和std::future**是C11里另一个高频考点。面试官通常会问“std::async和手动std::thread相比有什么优势”答案是std::async能返回结果、能管理生命周期、底层可能复用线程池取决于实现策略。这里要能说出std::launch::async和std::launch::deferred两种启动策略的区别前者立刻在新线程执行后者延迟到调用future.get()时才同步执行。**std::atomic**解决的是无锁场景下的原子操作问题。面试官常问“为什么i在多线程下不安全”本质是i在CPU层面是读-改-写三步操作不是原子的。用std::atomicint可以保证原子性但很多人不知道fetch_add和operator在某些架构上性能差异很大因为后者可能涉及顺序一致性的开销。3.2 条件变量与唤醒机制高频考点的核心细节标题里特别提到了C11多线程唤醒的用法那这部分必须展开说明白。C11里最常用的线程通知机制是std::condition_variable基本用法分三步先加锁再判断条件不满足就wait()被唤醒后重新检查条件。我总结过一个面试必背的模板#include iostream #include thread #include mutex #include condition_variable std::mutex mtx; std::condition_variable cv; bool ready false; void worker() { std::unique_lockstd::mutex lock(mtx); cv.wait(lock, [] { return ready; }); std::cout worker proceeding std::endl; } int main() { std::thread t(worker); { std::lock_guardstd::mutex lock(mtx); ready true; } cv.notify_one(); t.join(); return 0; }注意两点wait()必须搭配std::unique_lock不能搭配std::lock_guardwait()的第二个参数是一个返回bool的谓词用来防止虚假唤醒。这两点几乎每次都考尤其是第一点很多人知道要用unique_lock但说不清为什么——因为wait()在阻塞时要把锁释放掉让其他线程能进入临界区修改条件而lock_guard不支持手动解锁和重新加锁unique_lock天生就是为这种场景设计的。notify_one()和notify_all()也是必考点。前者只唤醒一个等待线程后者唤醒所有。实际工程中用notify_all更常见因为多等待线程时的“惊群效应”有时候反而是业务需要的。面试官会追问“如果notify_one()唤醒的线程因为条件不满足又继续等待了怎么办”标准回答是“用带谓词的wait重查条件这正是为什么要用谓词版本”。另外面试中还经常问到超时等待。std::condition_variable提供了wait_for()和wait_until()区别是前者接收时长、后者接收时间点。一个常见场景是“工作线程等待任务队列如果2秒内没有新任务就超时退出”这里就必须用wait_for()。注意返回值是std::cv_status::timeout还是std::cv_status::no_timeout很多人忽略这一点然后临时翻文档。3.3 C11锁家族lock_guard、unique_lock、shared_lock怎么选锁是C11多线程面试的另一个密集考点。我把常见的锁类整理成一张对比表面试前过一遍非常高效锁类型特性适用场景面试常问点std::mutex最基本的互斥锁不可递归临界区保护为什么不建议直接操作它std::recursive_mutex可递归加锁同一线程可多次lock递归函数中的临界区滥用会导致逻辑混乱std::timed_mutex支持超时尝试加锁防止无限期等待try_lock_for()用法std::lock_guardRAII封装构造加锁析构解锁简单作用域内保护为什么比手动lock/unlock安全std::unique_lock更灵活的RAII可手动解锁/重锁配合条件变量defer_lock参数的含义std::shared_lock共享锁多个读者可同时持有读多写少场景和unique_lock搭配使用std::scoped_lockC17引入可同时锁多个互斥量避免多个锁造成死锁与lock_guard的区别面试中最经典的问题就是**“说一下lock_guard和unique_lock的区别”**。我的回答套路是先说共同点两者都是RAII风格的锁管理工具构造时加锁、析构时自动解锁。然后说关键区别unique_lock更灵活支持手动unlock()和再次lock()还能和std::condition_variable::wait()配合使用lock_guard则没有任何额外操作仅在构造和析构时加解锁。另一个高频追问是“如何避免死锁”。除了最经典的“使用统一的锁顺序”之外C11还提供了std::lock函数可以一次性锁定多个互斥量避免因为加锁顺序不一致导致死锁。C17更是加了std::scoped_lock直接管理多个锁的生命周期。这两个方案面试中能说出来就比只答“按顺序加锁”高一个段位。4. Java、Python、Linux、QT多线程面试官最爱横向对比4.1 Java多线程synchronized与Lock的相爱相杀Java多线程面试是Java岗的重头戏但即使你不是应聘Java岗,了解Java的多线程模型也有助于加深对并发概念的理解。Java的synchronized关键字是最基础的同步方式它锁的是对象头底层依赖JVM的monitor机制。面试官通常会问“synchronized修饰静态方法和实例方法有什么区别”“synchronized是可重入的吗”。第一个问题的答案是静态方法锁的是Class对象实例方法锁的是当前实例第二个问题的答案是“可重入”因为JVM会记录锁的持有线程和重入次数同一个线程再次进入时直接计数加一。java.util.concurrent.locks.Lock接口则是JDK 5之后提供的显式锁方案。ReentrantLock是最典型的实现它比synchronized多了可中断锁、公平锁、超时获取锁等能力。面试里最常问的是“synchronized和ReentrantLock怎么选”我的建议是默认用synchronized因为它更简单、会自动释放锁JVM也在持续优化需要超时、可中断、多个条件队列时再用ReentrantLock。Java线程池也是必考项。ThreadPoolExecutor的七个构造参数——corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler——每一个都要能解释清楚尤其是“任务提交后线程池的处理流程”先判断核心线程是否满然后判断工作队列是否满最后判断最大线程数是否满满了走拒绝策略。这道题几乎每面必问。4.2 Python多线程GIL到底是背锅侠还是挡箭牌Python多线程面试题最大的坑在于GILGlobal Interpreter Lock全局解释器锁。大部分人对Python多线程的理解就是“Python的多线程是假的因为有GIL”。这句话本身不算错但不完整面试官真正想听到的是GIL是CPython解释器层面的一个互斥锁保证同一时刻只有一个线程在执行Python字节码。它存在的原因是CPython的内存管理引用计数不是线程安全的GIL用最简单粗暴的方式保证了安全性代价是牺牲了多核并行计算能力。但GIL只锁住Python字节码的执行不锁系统调用和I/O操作所以I/O密集型的任务用Python多线程依然能提速CPU密集型的任务用多线程反而可能变慢因为线程切换本身有开销。这里有个面试官经常埋的坑“那Python里怎么实现真正的并行”答案是multiprocessing模块它通过多进程绕过GIL利用多核CPU。另一个答案是concurrent.futures它封装了线程池和进程池可以统一风格的提交任务。Python的threading模块中的锁、条件变量、信号量、队列和Java/C的对应机制在逻辑上是同构的。queue.Queue是Python里最常用的线程安全队列底层依赖threading.Lock和threading.Condition实现。面Python岗位时能说出queue.Queue底层实现的两把锁一把保护队列本身、一把保护元数据就是一个很亮眼的加分点。4.3 Linux多线程从pthread_create到线程同步的本质如果面试官问操作系统层面的多线程那基本上就是在考Linux的POSIX线程pthread。C语言层面的pthread_create、pthread_join、pthread_mutex_lock、pthread_cond_wait这些API虽然老但它们是多线程的“母语”理解它们再看任何高级语言的多线程设计都会觉得豁然开朗。Linux线程的本质是轻量级进程。面试官常问“Linux的线程和进程有什么区别”答案是线程与进程共享地址空间但各自拥有独立的栈和寄存器上下文从内核调度的角度看线程本质上也是任务task通过clone()系统调用创建只需要传入不同的标志位控制是否共享内存空间。这个问题如果展开还能聊到vfork、写时复制、线程组等进阶概念。Linux多线程面试中最常见的编程题是**“用pthread实现生产者消费者模型”**。标准答案的骨架是一个互斥锁pthread_mutex_t保护共享队列一个条件变量pthread_cond_t用于生产者通知消费者另一个条件变量用于消费者通知生产者。生产者在队列满时pthread_cond_wait消费者在队列空时pthread_cond_wait。这个模型你会了C11和Java版本再看pthread版本就只是语法层面的翻译。另外Linux下还有一套独特的同步机制sem_t信号量、futex快速用户空间互斥锁、pthread_rwlock_t读写锁。面试官可能追问“信号量和互斥锁的区别”——信号量是计数器可以被多个线程同时获取大于1的计数互斥锁只能被一个线程持有信号量既可以做互斥也可以做同步互斥锁只解决互斥问题。4.4 QT多线程UI线程和工作线程的界限QT是C的一个应用框架在桌面应用和嵌入式界面领域用得很多。QT多线程面试的独特之处在于它引入了信号槽机制这是其他语言没有的概念。只要涉及UI操作就必须在主线程GUI线程中进行。QT禁止在工作线程中直接操作UI控件否则程序会崩溃或者出现难以调试的界面异常。想从工作线程通知UI更新正确的做法是通过信号槽连接让QT的事件循环来决定槽函数在哪个线程执行。QT里创建工作线程的方式有几种面试常问的包括继承QThread并重写run()、使用QObject::moveToThread()、使用QtConcurrent::run()。我的经验是工程上最推荐moveToThread方式因为它能把任务对象和线程生命周期解耦比继承QThread更灵活。但很多老项目还是直接继承QThread这块看岗位要求。QT的线程同步则通过QMutex、QReadWriteLock、QSemaphore、QWaitCondition等类实现这些就是前面说的C锁和条件变量的QT封装版。面试时如果能顺带说一句“QWaitCondition的wait()函数底层就是pthread_cond_wait所以使用时同样要注意虚假唤醒问题”面试官通常会眼前一亮——这说明你既有框架层面的经验也有底层认知。5. 面试必问的经典场景题从原理到代码5.1 生产者消费者所有线程协作题的母题多线程面试题如果只背一道那一定是生产者消费者。不管面试官用什么语言让你写核心思路都是同一个用一个线程安全的缓冲区生产者往里放数据消费者从中取数据缓冲区满时生产者阻塞缓冲区空时消费者阻塞。C11版本我前面给过条件变量的示例这里再画一下完整结构#include iostream #include thread #include mutex #include condition_variable #include queue std::queueint buffer; std::mutex mtx; std::condition_variable cv_producer; std::condition_variable cv_consumer; const int MAX_SIZE 10; void producer(int id) { for (int i 0; i 20; i) { std::unique_lockstd::mutex lock(mtx); cv_producer.wait(lock, [] { return buffer.size() MAX_SIZE; }); buffer.push(i); std::cout Producer id produced i std::endl; cv_consumer.notify_one(); } } void consumer(int id) { for (int i 0; i 20; i) { std::unique_lockstd::mutex lock(mtx); cv_consumer.wait(lock, [] { return !buffer.empty(); }); int value buffer.front(); buffer.pop(); std::cout Consumer id consumed value std::endl; cv_producer.notify_one(); } }为什么需要两个条件变量而不是一个这是个很好的面试追问点。如果用同一个条件变量当生产者放了数据后调用notify_one()可能唤醒的是另一个生产者而不是消费者造成“通知失灵”。用两个条件变量分别挂起生产者和消费者唤醒时就能精准分类。虽然在只有一个生产者和一个消费者的场景下用一个条件变量也能工作但多生产者多消费者时就会出现性能抖动甚至逻辑错误。面试官还会问“缓冲区用什么数据结构”。最标准的是std::queue加互斥锁工程上还有无锁环形队列boost::lockfree::spsc_queue等方案。能在回答里提到“如果对性能要求极高可以改成无锁队列”会显得你有广度。5.2 线程安全的单例模式从懒汉到双重检查锁单例模式是所有设计模式里和并发关系最紧密的一个几乎每场面试都会碰到。从线程安全的角度单例写法可以分为几代第一代懒汉式单例getInstance里先判断实例是否为空然后创建。这种写法线程不安全两个线程可能同时进入if分支各自创建一份实例。第二代加锁懒汉式整个方法加锁线程安全了但每次获取实例都要抢锁性能差。第三代双重检查锁class Singleton { private: static std::atomicSingleton* instance; static std::mutex mtx; public: static Singleton* getInstance() { Singleton* tmp instance.load(std::memory_order_acquire); if (tmp nullptr) { std::lock_guardstd::mutex lock(mtx); tmp instance.load(std::memory_order_relaxed); if (tmp nullptr) { tmp new Singleton(); instance.store(tmp, std::memory_order_release); } } return tmp; } };这里有个深入考点为什么双重检查锁在C里要加std::atomic而不是用裸指针因为new Singleton()在CPU指令层面不是原子的很可能先分配内存、返回指针再执行构造函数。另一个线程看到指针非空直接拿着用但构造函数还没跑完就出问题了。C11要求把单例指针声明为std::atomic并使用memory_order_acquire和memory_order_release来保证指令顺序。Java里则对应volatile关键字面试官常问“Java单例双重检查锁为什么需要volatile”答案完全一样。第四代是C11的Magic Static局部静态变量。C11标准规定局部静态变量的初始化是线程安全的编译器会生成一个隐藏的锁或原子操作保证初始化只执行一次。所以现代C推荐直接用局部静态变量实现单例代码最短、最安全class Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } };这题在面试中的加分点就在于你能从第一代写到第四代并说清楚每一代解决了什么问题、引入了什么新问题。这体现的是演进的思维能力。5.3 交替打印与顺序执行线程协作面试小题集除了生产者消费者还有几个小而精的线程协作题目在面试中出场率极高两个线程交替打印奇数和偶数。基本思路是用两个条件变量或者用一个条件变量加一个计数器。这里考点是wait谓词的写法以及防止“忙等”轮询消耗CPU。很多人第一反应是用while (true)加if判断然后sleep(1)这在功能上能实现但工程上很差——面试官一定会追问“除了sleep还有别的办法吗”答案是条件变量或信号量。三个线程按顺序打印A、B、C。这题比奇偶数稍微難一点因为牵扯到三个线程之间的精确调度。用三个条件变量加一个状态标志位可以解决核心是每个线程等待“状态等于自己编号”打印后更新状态并唤醒下一个。这题的价值在于考察你对多个条件变量配合的理解以及对“唤醒扩散”的控制。主线程等待多个子线程执行完毕。C11里可以用std::thread::join()逐个等待更优雅的是std::promise/std::future组合或者std::barrierC20引入。Java里则用CountDownLatch或CyclicBarrier这两种的对比也是经典面试题——前者是一次性的后者可以循环使用前者用于等待事件后者用于等待线程集合同步。5.4 死锁四个必要条件与实战排查方法死锁面试题无论怎么问底层都是那四个必要条件互斥、持有并等待、不可剥夺、循环等待。能准确背出这四个条件的人很多但要真正理解“为什么这四个条件缺一不可”需要配合一个实际案例。一个经典的死锁场景线程A持有锁1等待锁2线程B持有锁2等待锁1。两个线程互相等待谁都无法继续。这题面试官会问“如何解决”答案无非是四种——破坏任意一个条件。实际工程中最常用的是破坏循环等待也就是给所有锁规定统一的获取顺序其次是破坏持有并等待一次性获取所有锁C的std::lock和Java的Lock接口组合可以实现破坏“不可剥夺”比较难因为锁的本质就是不可剥夺破坏“互斥”只在特定场景可行如无锁编程、读写锁降级。还有一个加分技巧如何排查生产环境中的死锁Java里可以用jstack把线程栈打出来搜索“Found one Java-level deadlock”字样能看到死锁的线程和锁的持有关系C里可以用gdb附加进程thread apply all bt查看所有线程的调用栈。能把这套排查流程说清楚面试官会觉得你不仅是理论的巨人还有实战经验。6. 多线程面试中我踩过的坑与避坑清单6.1 五个最容易翻车的细节这些年我自己面试和被面发现有些细节看着不起眼但一翻车就是致命伤。整理成避坑清单备考时逐条自查第一个坑wait()和sleep()的区别说不全。两者最大的区别是wait()释放锁sleep()不释放锁wait()是Object的方法必须在同步块中调用sleep()是Thread的静态方法哪里都能调wait()可以被notify()唤醒sleep()只能等时间到了自然醒来。面试官问这道题至少要说全两三点才稳。第二个坑notify()唤醒后锁还没释放。notify()只负责唤醒不负责释放锁。被唤醒的线程要等当前线程退出同步块释放锁之后才能真正抢到锁继续执行。很多人以为notify()一调用对方立刻执行——这是错的被唤醒的线程进入的是“可运行状态”而非“运行状态”。第三个坑在线程回调里直接操作UI控件。这是桌面应用面试最常见的坑尤其是QT和Swing。任何UI框架都要求UI操作必须在主线程完成工作线程只能通过消息投递或信号槽把数据转发到主线程。第四个坑锁的粒度控制不当。面试官给你一段代码问“性能瓶颈在哪”答案常常是“锁的粒度太大”。一个for循环里把整个循环体都锁住并发度就没了应该只锁共享变量的读写那一小段代码。这个点非常考验工程理解。第五个坑线程池参数凭感觉设。面试官问“线程池核心线程数怎么定”标准回答是CPU密集型任务设置CPU核心数1IO密集型任务设置2倍CPU核心数或更高但实际还要看任务队列长度、内存占用、QPS目标。就算不给具体数字也要能说出计算逻辑。6.2 我的备考策略三轮递进复习法最后分享我自己的备考方法不一定适合所有人但对大多数技术面试是有效的。第一轮框架构建。不要直接扎进题目里背答案先把文章开头说的五大维度列成思维导图每个维度下面写清楚相关知识点。这一轮的目标是做到“看到任何一道多线程题能说出它属于哪个维度、还可能引申出哪些问题”。这是我面别人时观察到的规律——拿到题先归类答案的结构感就很强不容易跑偏。第二轮代码落地。多线程是实践性极强的板块光背不行。把面试高频的几个代码模型——生产者消费者、交替打印、死锁模拟、线程安全单例——在自己的机器上各写一遍用C写一遍、再用你主语言写一遍。写完用断点跟踪的方式跑一遍观察线程状态切换很多“背不下来的细节”会自然记住。第三轮模拟问答。把每个知识点整理成问答卡片找一个朋友或者对着镜子提问。重点练“为什么”比如不满足于“unique_lock可以在wait时释放锁”要追问“为什么lock_guard不行”“wait内部到底做了什么”——这一步能把死记硬背转化为真正的理解。多线程的面试准备没有捷径但也不需要恐惧。把核心机制吃透再配上一轮代码实操你完全可以从容应对市面上绝大多数多线程面试题。我个人连续面过的几次技术面问的就是这篇文章里覆盖的这些模型和细节区别只在于出题角度的排列组合。把这个框架打牢后面无论遇到什么语言、什么框架的并发问题你都能用自己的逻辑推出来。