C++多线程同步与互斥

在多线程编程中,C++多线程同步与互斥用于控制多个线程对共享资源的访问,以避免数据竞争、死锁等问题。可先参考之前博客 C++多线程运行整理

1. 互斥量(Mutex)

互斥量是一种同步原语,用于防止多个线程同时访问共享资源。当一个线程需要访问共享资源时,它首先需要锁定(lock)互斥量。如果互斥量已经被其他线程锁定,那么请求锁定的线程将被阻塞,直到互斥量被解锁(unlock)。

std::mutex:用于保护共享资源,防止数据竞争。

std::lock_guard 和 std::unique_lock:自动管理锁的获取和释放

python 复制代码
/*
线程管理
join() 用于等待线程完成执行,如果不调用join() 或detach(),而直接销毁线程对象,会导致系统崩溃.
detach() 将线程与主线程分离,线程在后台独立运行,主线程不再等待它.

线程同步与互斥
1.互斥量 Muter
如果互斥量已经被其他线程锁定,那么请求锁定的线程将被阻塞,直到互斥量被解锁(unlock)
std::mutex:用于保护共享资源,防止数据竞争
std::lock_guard 和 std::unique_lock:自动管理锁的获取和释放。

2.锁 Locks
多种锁类型,用于简化互斥量的使用和管理
std::lock_guard 作用域锁,当构建时自动锁定互斥量,当析构时自动解锁
std::unique_lock 与std::lock_guard 类型,但是提供更多的灵活性(如可转移所有权和手动解锁)

3.条件变量 Condition Variable
条件变量用于 线程之间的协调,允许一个或多个线程等待某个条件的发生.通常与互斥量一起使用,以实现线程同步.
std::condition_variable 用于实现线程间的等待和通知机制

4. 原子操作(Atomic Operations)
原子操作确保对共享数据的访问是不可分割的,即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。

5. 线程局部存储(Thread Local Storage, TLS)
线程局部存储允许每个线程拥有自己的数据副本。这可以通过thread_local关键字实现,避免了对共享资源的争用。

6. 死锁(Deadlock)和避免策略
死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:
a.总是以相同的顺序请求资源。
b.使用超时来尝试获取资源。
c.使用死锁检测算法。

*/
#include <mutex>
#include <iostream>
#include <thread>

std::mutex mtx;//全局互斥量

void sateFunction(){
    mtx.lock(); //锁定互斥锁
    //访问或者修改共享资源
    mtx.unlock(); //释放互斥所
}


int main()
{
    std::thread t1(sateFunction);
    std::thread t2(sateFunction);
    t1.join();
    t2.join();
    return 0;
}
python 复制代码
//多线程_互斥量

#include <iostream>       // for std::cout, std::endl
#include <mutex>            // for std::mutex, std::lock_guard
#include <thread>           // for std::thread, std::this_thread::sleep_for

// 全局互斥锁,保护共享资源(如 std::cout)
std::mutex mtx;

// 全局共享资源
int shared_counter = 0;

/**
 * @brief 安全的线程函数(使用 RAII 的 lock_guard)
 *         推荐做法:使用 lock_guard,自动加锁/解锁
 */
void safeFunction() {
    // 使用 RAII:lock_guard 在构造时自动加锁,析构时自动解锁
    std::lock_guard<std::mutex> lk(mtx);

    //  此处可以安全访问或修改共享资源
    ++shared_counter;
    std::cout << " 线程 " << std::this_thread::get_id()
              << " 访问共享资源,当前计数: " << shared_counter << std::endl;

    // 模拟一些工作耗时
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
}

int main() {
    std::cout << " 主线程开始执行 (ID: " << std::this_thread::get_id() << ")\n\n";

    // 创建两个线程
    std::thread t1(safeFunction);
    std::thread t2(safeFunction);

    // 等待两个线程完成
    t1.join();
    t2.join();

    std::cout << "\n 所有线程执行完毕,主线程退出。\n";
    std::cout << " 最终共享计数: " << shared_counter << std::endl;

    return 0;
}

2. 锁(Locks)

C++提供了多种锁类型,用于简化互斥量的使用和管理。

常见的锁类型包括:

• std::lock_guard:作用域锁,当构造时自动锁定互斥量,当析构时自动解锁。

• std::unique_lock:与std::lock_guard类似,但提供了更多的灵活性,例如可以转移所有权和手动解锁。

python 复制代码
/*
线程管理
join() 用于等待线程完成执行,如果不调用join() 或detach(),而直接销毁线程对象,会导致系统崩溃.
detach() 将线程与主线程分离,线程在后台独立运行,主线程不再等待它.

线程同步与互斥
2.锁 Locks
多种锁类型,用于简化互斥量的使用和管理
std::lock_guard 作用域锁,当构建时自动锁定互斥量,当析构时自动解锁
std::unique_lock 与std::lock_guard 类型,但是提供更多的灵活性(如可转移所有权和手动解锁)
*/

// #include <mutex>
// #include <thread>

// std::mutex mtx;
// void safeFunctionWithLockGuard()
// {
//     std::lock_guard<std::mutex> lk(mtx);
//     // 访问或修改共享资源

// }

// void safeFunctionWithUniqueLock(){
//     std::unique_lock<std::mutex> ul(mtx);
//     //访问或修改共享资源
//     ul.unlock(); //可选:手动解锁
//     //...
// }

// int main()
// {

//     return 0;
// }

#include <iostream>       // for std::cout, std::endl
#include <mutex>            // for std::mutex, std::lock_guard, std::unique_lock
#include <thread>           // for std::thread, std::this_thread::sleep_for
#include <chrono>           // for std::chrono::milliseconds

// 全局互斥锁,保护共享资源
std::mutex mtx;

/**
 * @brief 使用 lock_guard 的安全函数
 *        lock_guard 是 RAII 风格的互斥锁,构造时自动加锁,
 *        析构时自动解锁,防止忘记解锁导致死锁。
 */
void safeFunctionWithLockGuard() {
    // 构造 std::lock_guard 时自动调用 mtx.lock()
    std::lock_guard<std::mutex> lk(mtx);

    //  此处可以安全访问或修改共享资源
    std::cout << " [lock_guard] 线程 " << std::this_thread::get_id()
              << " 正在访问共享资源...\n";

    // 模拟一些工作耗时(可选)
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

/**
 * @brief 使用 unique_lock 的安全函数
 *        unique_lock 比 lock_guard 更灵活,支持:
 *        - 手动解锁(unlock())
 *        - 手动加锁(lock())
 *        - 移动所有权(move-constructible)
 *        - 与条件变量配合使用(cv.wait())
 */
void safeFunctionWithUniqueLock() {
    // 构造 unique_lock 时自动加锁
    std::unique_lock<std::mutex> ul(mtx);

    std::cout << " [unique_lock] 线程 " << std::this_thread::get_id()
              << " 正在访问共享资源...\n";

    // 模拟工作耗时
    std::this_thread::sleep_for(std::chrono::milliseconds(150));

    // 可选:手动解锁(例如:想让其他线程短暂访问)
    ul.unlock();
    std::cout << " [unique_lock] 手动解锁,允许其他线程访问\n";

    // 假设在此期间有其他线程尝试访问(可观察输出顺序)
    std::this_thread::sleep_for(std::chrono::milliseconds(50));

    //  再次加锁(恢复保护)
    ul.lock();
    std::cout << " [unique_lock] 重新加锁,继续操作\n";

    // 作用域结束,析构时自动解锁(即使未显式调用 unlock)
    std::cout << " [unique_lock] 线程 " << std::this_thread::get_id()
              << " 完成操作,自动解锁\n";
}

int main() {
    std::cout << " 主线程开始执行 (ID: " << std::this_thread::get_id() << ")\n\n";

    // 创建两个线程,分别调用不同锁函数
    std::thread t1(safeFunctionWithLockGuard);
    std::thread t2(safeFunctionWithUniqueLock);

    // 等待两个线程完成
    t1.join();
    t2.join();

    std::cout << "\n 所有线程执行完毕,主线程退出。\n";

    return 0;
}
python 复制代码
//多线程_锁(生产者消费者)
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <functional>

// 共享缓冲区(队列)
std::queue<int> buffer;
std::mutex mtx;                    // 保护队列的互斥锁
std::condition_variable not_empty; // 条件变量:当队列非空时通知消费者
std::condition_variable not_full;  // 条件变量:当队列不满时通知生产者

// 缓冲区最大容量
const int MAX_SIZE = 5;
bool done = false; // 标志:生产者是否完成

/**
 * @brief 生产者线程函数
 *        模拟生产数据并放入缓冲区
 */
void producer(int id) {
    for (int i = 1; i <= 10; ++i) {
        // 模拟生产耗时
        std::this_thread::sleep_for(std::chrono::milliseconds(200));

        // 1. 加锁,准备向队列中放入数据
        std::unique_lock<std::mutex> lk(mtx);

        // 2. 等待队列未满(避免缓冲区溢出)
        not_full.wait(lk, []{ return buffer.size() < MAX_SIZE; });

        // 3. 放入数据
        buffer.push(i);
        std::cout << "生产者 " << id << " 生产了数据: " << i 
                  << " (当前队列大小: " << buffer.size() << ")\n";

        // 4. 通知消费者:队列中有数据了
        not_empty.notify_one();
    }

    // 生产结束,通知所有消费者
    {
        std::lock_guard<std::mutex> lg(mtx);
        done = true;
    }
    not_empty.notify_all(); // 唤醒所有等待的消费者
}

/**
 * @brief 消费者线程函数
 *        从缓冲区取出数据并处理
 */
void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lk(mtx);

        // 1. 等待队列非空,或生产者已结束
        not_empty.wait(lk, []{ 
            return !buffer.empty() || done; 
        });

        // 2. 如果队列为空且生产者已完成,则退出
        if (buffer.empty() && done) {
            std::cout << "消费者 " << id << " 收到结束信号,停止工作。\n";
            break;
        }

        // 3. 取出数据
        int data = buffer.front();
        buffer.pop();
        std::cout << "消费者 " << id << " 消费了数据: " << data 
                  << " (剩余队列大小: " << buffer.size() << ")\n";

        // 4. 通知生产者:队列有空位了
        not_full.notify_one();
    }
}

int main() {
    std::cout << "=== 生产者-消费者模型开始运行 ===\n";
    std::cout << "缓冲区最大容量: " << MAX_SIZE << "\n";
    std::cout << "生产者线程数: 2\n";
    std::cout << "消费者线程数: 3\n\n";

    // 创建线程
    std::thread prod1(producer, 1);
    std::thread prod2(producer, 2);
    std::thread cons1(consumer, 1);
    std::thread cons2(consumer, 2);
    std::thread cons3(consumer, 3);

    // 等待所有线程完成
    prod1.join();
    prod2.join();
    cons1.join();
    cons2.join();
    cons3.join();

    std::cout << "\n=== 生产者-消费者模型执行完毕 ===\n";

    return 0;
}

3. 条件变量(Condition Variable)

条件变量用于线程间的协调,允许一个或多个线程等待某个条件的发生。它通常与互斥量一起使用,以实现线程间的同步。

std::condition_variable用于实现线程间的等待和通知机制。

python 复制代码
/*
3.条件变量 Condition Variable
条件变量 用于线程间的协调,允许一个或者多个线程等待某个条件的发生.通常与互斥量一起使用,以实现线程间的同步.
std::condition_variable用于实现线程间的等待和通知机制。
std::condition_variable 必须在 std::unique_lock 作用域内使用
*/

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void workerThread()
{
    std::unique_lock<std::mutex> lk(mtx);
    cv.wait(lk, []{ return ready; }); // 等待条件满足
    std::cout << "工作线程:数据已准备,开始执行...\n";
    // 模拟工作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "工作线程:任务完成!\n";
}

void mainThread()
{
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟准备时间

    {
        std::lock_guard<std::mutex> lg(mtx);
        std::cout << "主线程:数据准备完成,设置 ready = true\n";
        ready = true;
    } // lock_guard 释放锁

    cv.notify_one(); // 通知一个等待的线程
    std::cout << "主线程:已通知工作线程!\n";
}

int main()
{
    std::thread worker(workerThread);
    std::thread main_t(mainThread);

    worker.join();
    main_t.join();

    return 0;
}

4. 原子操作(Atomic Operations)

原子操作确保对共享数据的访问是不可分割的,即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。

python 复制代码
/*
4.原子操作 Atomic Operations
原子操作确保对共享数据的访问是不可分割的,即多线程环境下,原子操作只能完全执行或完全不执行.
*/

#include <atomic>
#include <thread>
#include <iostream>  
std::atomic<int> count(0);//声明一个共享变量 count

void increment() {
    count.fetch_add(1, std::memory_order_relaxed);
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();

    // 使用 count.load() 显式获取值
    std::cout << "多线程原子操作: " << count << std::endl;
    std::cout << "多线程原子操作: " << count.load() << std::endl;
    return 0; // 显式返回值,更清晰
}
python 复制代码
//多线程_原子操作

#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>

std::atomic<int> count{0};
std::mutex cout_mutex;

void increment() {
    count.fetch_add(1, std::memory_order_relaxed);
    std::lock_guard<std::mutex> lk(cout_mutex);
    std::cout << "线程 " << std::this_thread::get_id()
              << ",计数: " << count.load() << '\n';
}

int main() {
    std::cout << "主线程开始 (ID: " << std::this_thread::get_id() << ")\n";
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "\n操作完成\n";
    std::cout << "最终计数: " << count.load() << '\n';
    return 0;
}

5. 线程局部存储(Thread Local Storage, TLS)

线程局部存储允许每个线程拥有自己的数据副本。这可以通过thread_local关键字实现,避免了对共享资源的争用。

python 复制代码
/*
5.线程局部存储 Tread Local Storage(TLS)
线程局部存储 允许每个线程拥有自己的数据副本,也可以通过 thread_local关键字来实现,避免对共享资源的争用.
*/

#include <iostream>
#include <thread>

thread_local int threadData = 0;
thread_local int threadData1 = 0;

void threadFunction()
{
    threadData = 42;//每个线程都有自己的threadData副本
    std::cout << "线程数据:" << threadData << std::endl; 
}

void threadFunction1()
{
    threadData1 = 53;//每个线程都有自己的threadData副本
    std::cout << "线程数据:" << threadData1 << std::endl; 
}

int main()
{
    std::thread t1(threadFunction);//线程42
    t1.join();
    std::thread t2(threadFunction1);//线程53
    t2.join();
    return 0;
}

6. 死锁(Deadlock)和避免策略

死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:

a.总是以相同的顺序请求资源。

b.使用超时来尝试获取资源。

c.使用死锁检测算法。

python 复制代码
/*
6. 死锁(Deadlock)和避免策略
死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:
a.总是以相同的顺序请求资源。
b.使用超时来尝试获取资源。
c.使用死锁检测算法。
*/

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

// 使用 timed_mutex 替代 mutex,支持超时机制
std::timed_mutex account1, account2;

//  演示死锁:两个线程以不同顺序请求锁
void transfer_with_deadlock(int amount) {
    std::cout << "线程 " << std::this_thread::get_id()
              << " 尝试转账 " << amount << " 元\n";

    account1.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    account2.lock();

    std::cout << "转账成功:从 account1 转 " << amount
              << " 到 account2\n";

    account2.unlock();
    account1.unlock();
}

//  策略 a:总是以固定顺序获取锁(避免死锁)
void transfer_with_ordering(int amount) {
    std::cout << "线程 " << std::this_thread::get_id()
              << " 使用固定顺序锁尝试转账 " << amount << " 元\n";

    // 固定顺序:先获取 account1,再获取 account2
    account1.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    account2.lock();

    std::cout << "转账成功:从 account1 转 " << amount
              << " 到 account2(固定顺序)\n";

    account2.unlock();
    account1.unlock();
}

//  策略 b:使用超时机制尝试获取锁
bool try_transfer_with_timeout(int amount, int timeout_ms) {
    std::cout << "线程 " << std::this_thread::get_id()
              << " 使用超时尝试转账 " << amount << " 元\n";

    // 尝试获取 account1,超时 150ms
    auto deadline = std::chrono::steady_clock::now()
                     + std::chrono::milliseconds(timeout_ms);
    if (!account1.try_lock_until(deadline)) {
        std::cout << "获取 account1 锁失败(超时),放弃本次尝试\n";
        return false;
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(50));

    // 尝试获取 account2,超时 150ms
    if (!account2.try_lock_until(deadline)) {
        std::cout << "获取 account2 锁失败(超时),释放 account1 锁并放弃\n";
        account1.unlock();
        return false;
    }

    std::cout << "转账成功:从 account1 转 " << amount
              << " 到 account2(使用超时机制)\n";

    account2.unlock();
    account1.unlock();
    return true;
}

//  策略 c:简化死锁检测(模拟)
void transfer_with_deadlock_detection() {
    std::cout << "线程 " << std::this_thread::get_id()
              << " 使用死锁检测机制尝试转账\n";

    // 尝试获取 account1 锁,最大等待 100ms
    if (account1.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "成功获取 account1 锁\n";

        // 尝试获取 account2 锁,最大等待 100ms
        if (account2.try_lock_for(std::chrono::milliseconds(100))) {
            std::cout << "成功获取 account2 锁,转账成功\n";
            account2.unlock();
        } else {
            std::cout << "无法获取 account2 锁,可能死锁风险!\n";
            account1.unlock();
            std::cout << "释放 account1 锁,避免死锁\n";
        }
    } else {
        std::cout << "无法获取 account1 锁,放弃尝试\n";
    }
}

int main() {
    std::cout << "=== 死锁演示与三种避免策略 ===\n\n";

    // 测试 1:死锁演示
    std::cout << "1. 演示死锁:两个线程以不同顺序请求锁\n";
    std::thread t1([&]() { transfer_with_deadlock(100); });
    std::thread t2([&]() { transfer_with_deadlock(200); });
    t1.join();
    t2.join();

    std::cout << "\n" << std::string(60, '-') << "\n\n";

    // 测试 2:策略 a ------ 固定顺序获取锁
    std::cout << "2. 策略 a:固定顺序获取锁(避免死锁)\n";
    std::thread t3([&]() { transfer_with_ordering(150); });
    std::thread t4([&]() { transfer_with_ordering(250); });
    t3.join();
    t4.join();

    std::cout << "\n" << std::string(60, '-') << "\n\n";

    // 测试 3:策略 b ------ 使用超时机制
    std::cout << "3. 策略 b:使用超时机制尝试获取锁\n";
    std::thread t5([&]() { try_transfer_with_timeout(100, 150); });
    std::thread t6([&]() { try_transfer_with_timeout(200, 150); });
    t5.join();
    t6.join();

    std::cout << "\n" << std::string(60, '-') << "\n\n";

    // 测试 4:策略 c ------ 简化死锁检测
    std::cout << "4. 策略 c:使用死锁检测机制(模拟)\n";
    std::thread t7(transfer_with_deadlock_detection);
    t7.join();

    std::cout << "\n=== 所有测试完成 ===\n";
    return 0;
}

补充:线程间通信

std::future 和 std::promise:实现线程间的值传递。

python 复制代码
//使用 std::future 和 std::promise 实现线程通信
// 一个线程计算结果,另一个线程获取结果

#include <iostream>
#include <thread>
#include <future>
#include <vector>

int compute_data(int value) {
    // 模拟耗时计算
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    return value * 2;
}

int main() {
    // 创建 promise 和 future
    std::promise<int> result_promise;
    std::future<int> result_future = result_promise.get_future();
    // 启动计算线程
    std::thread worker([&result_promise](int input) {
        int output = compute_data(input);
        result_promise.set_value(output);  // 发送结果
    }, 10);

    // 主线程等待结果
    std::cout << "主线程:等待结果..." << std::endl;
    int result = result_future.get();  // 获取结果(阻塞)
    std::cout << "主线程:接收结果:" << result << std::endl;

    // 等待工作线程结束
    worker.join();
    return 0;
}
python 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>

// 全局共享数据:一个线程安全的队列
std::queue<int> data_queue;
std::mutex queue_mutex;
std::condition_variable queue_condition;

// 生产者线程函数
void producer(int id, int count) {
    for (int i = 0; i < count; ++i) {
        int value = id * 100 + i;

        // 加锁,写入数据
        std::lock_guard<std::mutex> lock(queue_mutex);
        data_queue.push(value);
        std::cout << "生产者" << id << " produced: " << value << std::endl;

        // 通知消费者有新数据
        queue_condition.notify_one();
    }
}

// 消费者线程函数
void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lock(queue_mutex);

        // 等待队列中有数据,或无数据时阻塞
        queue_condition.wait(lock, [] { return !data_queue.empty(); });

        // 取出数据
        int value = data_queue.front();
        data_queue.pop();

        lock.unlock(); // 释放锁

        // 处理数据
        std::cout << "消费者" << id << " consumed: " << value << std::endl;

        // 模拟处理耗时
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {
    // 创建多个生产者和消费者线程
    std::vector<std::thread> producers;
    std::vector<std::thread> consumers;

    // 启动 2 个生产者线程,每个生产 5 个数据
    for (int i = 0; i < 2; ++i) {
        producers.emplace_back(producer, i, 5);
    }

    // 启动 2 个消费者线程
    for (int i = 0; i < 2; ++i) {
        consumers.emplace_back(consumer, i);
    }

    // 等待生产者完成任务
    for (auto& t : producers) {
        t.join();
    }

    // 通知消费者停止(由于消费者使用无限循环,只能通过外部控制)
    // 为了安全终止,可以添加一个标志位,这里简化处理
    std::cout << "所有生产者已完成,等待消费者完成..." << std::endl;

    // 等待消费者线程(实际不会退出,除非手动终止)
    // 在真实场景中,应使用共享标志(如 atomic<bool>)来控制退出
    for (auto& t : consumers) {
        t.join();
    }

    return 0;
}

整理不易,诚望各位看官点赞 收藏 评论 予以支持,这将成为我持续更新的动力源泉。若您在阅览时存有异议或建议,敬请留言指正批评,让我们携手共同学习,共同进取,吾辈自当相互勉励!

相关推荐
赵文宇(温玉)5 小时前
构建内网离线的“github.com“,完美解决内网Go开发依赖
开发语言·golang·github
qq7422349845 小时前
Python操作数据库之pyodbc
开发语言·数据库·python
Joker100855 小时前
仓颉自定义序列化:从原理到高性能多协议实现
开发语言
Adellle5 小时前
2.单例模式
java·开发语言·单例模式
散峰而望5 小时前
C++入门(一)(算法竞赛)
c语言·开发语言·c++·编辑器·github
C_Liu_5 小时前
13.C++:继承
开发语言·c++
张人玉5 小时前
c#串口读写威盟士五插针
开发语言·c#·通讯
路长冬5 小时前
matlab与数字信号处理的不定期更新
开发语言·matlab·信号处理
卡卡酷卡BUG6 小时前
Java 后端面试干货:四大核心模块高频考点深度解析
java·开发语言·面试