记一次由于cas和线程调度出现的问题

先直接上代码:

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnSafeTest {

    private int i = 0;
    private static Unsafe UNSAFE;

    private static Long I_OFFSET;


    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe)field.get(null);
            //获取变量i的偏移量
            I_OFFSET = UNSAFE.objectFieldOffset(UnSafeTest.class.getDeclaredField("i"));
        }catch (Exception e){
        }
    }

    public static void main(String[] args) {
        final UnSafeTest test = new UnSafeTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    //位置1
                    boolean flag = UNSAFE.compareAndSwapInt(test,I_OFFSET,test.i,test.i+1);
                    System.out.println(flag+" "+Thread.currentThread().getName());
                    // 位置2
                    if (flag){
                        //获取主内存中的变量i的值
                        System.out.println(UNSAFE.getIntVolatile(test,I_OFFSET));
                    }
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"T2").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    //位置1
                    boolean flag = UNSAFE.compareAndSwapInt(test,I_OFFSET,test.i,test.i+1);
                    System.out.println(flag+" "+Thread.currentThread().getName());
                    //位置2
                    if (flag){
                        System.out.println(UNSAFE.getIntVolatile(test,I_OFFSET));
                    }
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"T1").start();
    }

}

两个线程T1和T2,启动后都以CAS的方式去修改UnSafeTest对象里面的属性i,正常来讲是应该交替执行,顺序打印的:

但是出现了另外一种情况,打印出了1,2,4,4 ,5...

就此情况进行分析,当线程T1和T2同时进行到位置1的时候,假设此时i的值为2,那么因为是cas,只有一个线程会修改成功i的值,如果是线程T1修改成功,那么T1的flag就是true,T2为false,此时,线程T1的cpu发生了调度,在位置2的地方等了会,刚刚等到线程T2进入下一个循环,将T1改后的i的值(此时为3)改成了4,那么就会出现同时打印两个4的场景。

出现问题的原因是打印操作和cas的判断操作不是原子操作,具有失败的可能性。

另外还有一个cas里面的问题,如果将i的类型由基本类型int改成Integer,那么cas全部失败。

 

 

1. TCP/UDP通信流程相关问题 可能提问: TCPUDP的区别是什么? TCP三次握手四次挥手的过程是怎样的?为什么是三次握手? 如何在C++中实现一个TCP服务器客户端? UDP通信是否可靠?如何保证数据包的有序完整? socket编程中常用的函数有哪些?例如bind(), listen(), accept()的作用? 2. 多线程编程相关问题 可能提问: std::thread如何创建管理线程线程如何传参? 多线程中如何避免资源竞争?什么是死锁?如何避免? std::mutexstd::lock_guard的使用场景是什么? std::map在多线程下是否线程安全?如何保证线程安全? 是否了解std::atomicCAS操作? 3. STL容器与C++语法相关问题 可能提问: std::mapstd::unordered_map的区别是什么?各自适用场景? std::map插入、查找的时间复杂度是多少? C++11新特性中你常用哪些?如std::shared_ptr、std::move、lambda表达式? 智能指针了解吗?unique_ptrshared_ptr的区别? 4. 系统设计与调试能力相关问题 可能提问: 后端模块如何处理并发连接?线程池了解吗?你有没有实现过? 项目中是如何管理用户房间信息的?是否考虑过性能瓶颈? 遇到过哪些多线程下的bug?你是如何调试并解决的? 如何保证代码的可维护性规范性?是否使用过日志系统? 5. 项目细节与个人贡献相关问题 可能提问: 你在项目中具体负责哪些模块?遇到了哪些挑战? 有没有参与系统性能优化?比如高并发下如何优化线程调度? 如果让你重新设计这个项目,你会改进哪些方面? 知识点 TCP/UDP通信流程:掌握网络通信的基本原理编程接口。 多线程编程:理解线程控制、同步机制资源共享问题。 STL容器使用:熟练使用标准库容器进行数据管理性能优化。 给出答案
最新发布
09-25
这是一个**普通文本型**问题,涉及多个综合性知识点的提问。以下是对所有问题的逐一解答: --- ### **TCPUDP的区别是什么?** - **连接性**:TCP是面向连接的协议,通信前需建立连接;UDP是无连接的。 - **可靠性**:TCP提供可靠传输(重传、确认机制);UDP不保证可靠性。 - **有序性**:TCP保证数据包顺序;UDP不保证。 - **速度与开销**:UDP更快、开销小;TCP因控制机制较多,延迟较高。 - **应用场景**:TCP适用于HTTP、文件传输等;UDP适用于视频流、DNS查询等。 --- ### **TCP三次握手四次挥手的过程是怎样的?为什么是三次握手?** #### 三次握手过程: 1. 客户端发送 `SYN=1, seq=x` → 服务端(请求建立连接) 2. 服务端回复 `SYN=1, ACK=1, seq=y, ack=x+1` 3. 客户端发送 `ACK=1, ack=y+1` > 此时双方进入连接状态。 #### 四次挥手机器: 1. 主动方发送 `FIN=1, seq=u` 2. 被动方回复 `ACK=1, ack=u+1` 3. 被动方处理完毕后发送 `FIN=1, seq=v` 4. 主动方回复 `ACK=1, ack=v+1`,等待2MSL后关闭 #### 为什么是三次握手? 防止已失效的连接请求突然传到服务器,造成资源浪费。三次握手可确保双方都具备**发送接收能力**。 --- ### **如何在C++中实现一个TCP服务器客户端?** #### TCP服务器基本流程: ```cpp socket() -> bind() -> listen() -> accept() -> recv()/send() -> close() ``` #### TCP客户端基本流程: ```cpp socket() -> connect() -> send()/recv() -> close() ``` 示例关键代码片段(简化): ```cpp // 服务器创建监听套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in addr{AF_INET, htons(8080), inet_addr("0.0.0.0")}; bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); listen(sockfd, 5); // 接受连接 int client_fd = accept(sockfd, nullptr, nullptr); char buffer[1024]; recv(client_fd, buffer, sizeof(buffer), 0); send(client_fd, "Hello", 6, 0); ``` 客户端调用 `connect()` 连接指定IP端口即可。 --- ### **UDP通信是否可靠?如何保证数据包的有序完整?** UDP本身不可靠、无序、不保证完整性。 要保证可靠性有序性,需在应用层实现: - 添加序列号标识数据包顺序 - 使用确认机制(ACK)超时重传 - 实现滑动窗口控制流量 - 校验检查完整性 例如:QUIC协议就是在UDP上实现的可靠传输。 --- ### **socket编程中常用的函数有哪些?例如bind(), listen(), accept()的作用?** | 函数 | 作用 | |------|------| | `socket()` | 创建套接字,返回文件描述符 | | `bind()` | 将套接字绑定到指定IP端口 | | `listen()` | 开始监听连接请求(用于TCP) | | `accept()` | 接受一个客户端连接,返回新的通信套接字 | | `connect()`| 客户端连接服务器 | | `send()/recv()` | 发送/接收数据(TCP) | | `sendto()/recvfrom()` | 发送/接收数据报(UDP) | --- ### **std::thread如何创建管理线程线程如何传参?** 使用 `std::thread t(func, args...)` 创建线程: ```cpp void func(int x, std::string s) { /* ... */ } std::thread t(func, 10, "hello"); t.join(); // 等待线程结束 ``` 传参方式:值传递、引用传递(需用 `std::ref`)、移动语义(`std::move`)。 注意:若未调用 `join()` 或 `detach()`,程序会终止。 --- ### **多线程中如何避免资源竞争?什么是死锁?如何避免?** - **资源竞争**:多个线程同时访问共享资源导致数据错乱。 - 解决方法:使用互斥锁(`std::mutex`)、原子操作等。 - **死锁**:两个或以上线程互相等待对方释放锁。 - 避免方法: - 锁顺序一致 - 使用超时尝试锁(`try_lock_for`) - 避免嵌套锁 --- ### **std::mutexstd::lock_guard的使用场景是什么?** - `std::mutex`:用于保护临界区,手动加锁解锁(`lock()/unlock()`)。 - `std::lock_guard`:RAII封装,构造时加锁,析构时自动解锁,防止忘解锁。 推荐始终使用 `std::lock_guard<std::mutex>` 自动管理锁。 ```cpp std::mutex mtx; { std::lock_guard<std::mutex> lock(mtx); // 操作共享资源 } // 自动解锁 ``` --- ### **std::map在多线程下是否线程安全?如何保证线程安全?** `std::map` 本身**不是线程安全的**。 - 多个线程读可以并发。 - 任意写操作必须加锁保护。 解决方案:使用 `std::mutex` 配合 `std::lock_guard` 控制访问。 --- ### **是否了解std::atomicCAS操作?** - `std::atomic<T>` 提供原子操作,保证读写不会被中断。 - 常用于计数器、标志位等轻量同步场景。 - 底层常基于 **CAS**(Compare-and-Swap)指令实现: ```cpp bool compare_exchange_weak(T& expected, T desired); ``` 若当前值等于 `expected`,则设为 `desired`,否则更新 `expected`。 优势:无锁编程,性能高,适合细粒度同步。 --- ### **std::mapstd::unordered_map的区别是什么?各自适用场景?** | 特性 | std::map | std::unordered_map | |------|----------|---------------------| | 底层结构 | 红黑树 | 哈希表 | | 时间复杂度 | $O(\log n)$ | 平均$O(1)$,最坏$O(n)$ | | 是否有序 | 是(按键排序) | 否 | | 是否可自定义哈希 | 不需要 | 需提供hash函数 | - `std::map`:需要有序遍历、范围查找时使用。 - `std::unordered_map`:追求高性能查找,不要求顺序时使用。 --- ### **std::map插入、查找的时间复杂度是多少?** - 插入:$O(\log n)$ - 查找:$O(\log n)$ 基于红黑树实现,每次操作平衡树结构。 --- ### **C++11新特性中你常用哪些?如std::shared_ptr、std::move、lambda表达式?** 常用C++11特性包括: - `std::shared_ptr` / `unique_ptr`:智能指针,自动管理内存 - `std::move`:移动语义,避免拷贝开销 - `lambda表达式`:匿名函数,便于STL算法配合使用 - `auto`:类型自动推导,简化代码 - `nullptr`:类型安全的空指针表示 --- ### **智能指针了解吗?unique_ptrshared_ptr的区别?** - `std::unique_ptr`:独占所有权,不可复制,只能移动;轻量高效。 - `std::shared_ptr`:共享所有权,引用计数管理生命周期;有性能开销。 使用原则: - 单个所有者用 `unique_ptr` - 多个对象共享资源用 `shared_ptr` - 注意循环引用问题(可用 `weak_ptr` 解决) --- ### **后端模块如何处理并发连接?线程池了解吗?你有没有实现过?** 处理并发方式: - 每连接一线程(简单但资源消耗大) - I/O多路复用(epoll/kqueue)+事件驱动(更高效) - 线程池预创建线程,避免频繁创建销毁 **线程池原理**: - 维护一组工作线程 - 任务队列存放待处理任务 - 线程从队列取任务执行 可实现一个带任务队列条件变量的通知机制线程池。 --- ### **项目中是如何管理用户房间信息的?是否考虑过性能瓶颈?** 通常使用: - `std::unordered_map<user_id, User>` 管理用户 - `std::unordered_map<room_id, Room>` 管理房间 - 房间内维护用户列表 性能优化点: - 使用哈希表加快查找 - 加锁粒度细化(每个房间独立锁) - 异步处理消息广播 瓶颈可能出现在高并发下的锁竞争,可通过分区、无锁结构优化。 --- ### **遇到过哪些多线程下的bug?你是如何调试并解决的?** 常见Bug: - 忘加锁导致数据竞争 - 死锁(两个线程交叉申请锁) - 竞态条件(启动顺序不确定) 调试手段: - 使用 `valgrind` + `helgrind` 检测数据竞争 - 日志录加解锁轨迹 - 断言单元测试验证线程安全 解决:统一锁顺序、减少临界区、使用工具检测。 --- ### **如何保证代码的可维护性规范性?是否使用过日志系统?** 措施: - 遵循命名规范(驼峰/下划线) - 函数职责单一,注释清晰 - 使用Git进行版本控制 - 编写单元测试 - 使用日志系统(如glog/spdlog)录运行状态 日志级别分类:DEBUG、INFO、WARN、ERROR,有助于线上排查问题。 --- ### **你在项目中具体负责哪些模块?遇到了哪些挑战?** (此题需结合个人经历回答,通用模板如下) 负责模块: - 网络通信层(TCP/UDP收发) - 用户管理与房间逻辑 - 多线程任务调度 挑战: - 高并发下消息丢失或乱序 - 死锁问题难以复现 解决方案: - 引入序列号+ACK机制 - 使用 `std::lock_guard` 统一锁管理 - 增加日志追踪执行流 --- ### **有没有参与系统性能优化?比如高并发下如何优化线程调度?** 优化方向: - 从“每连接一线程”改为线程池模型,降低上下文切换 - 使用 epoll 监听大量 socket - 对热点数据采用读写锁(`std::shared_mutex`) - 减少锁持有时间,拆分大临界区 结果:支持上千并发连接,CPU占用下降明显。 --- ### **如果让你重新设计这个项目,你会改进哪些方面?** 改进点: - 使用异步非阻塞I/O(如boost.asio)替代同步模型 - 引入消息队列解耦模块 - 数据持久化支持(数据库或Redis) - 支持分布式部署,提升扩展性 - 更完善的错误码心跳机制 --- ### **知识点详解** #### **TCP/UDP通信流程** 掌握网络通信的基本原理编程接口。 - `三次握手`确保双向通信可行;`四次挥手`确保全双工关闭。 - UDP需在应用层模拟可靠性机制。 #### **多线程编程** 理解线程控制、同步机制资源共享问题。 - 使用 `std::thread` 创建线程,`std::mutex` 保护共享资源。 - `std::atomic` 实现无锁编程,提高效率。 #### **STL容器使用** 熟练使用标准库容器进行数据管理性能优化。 - `std::map`有序但慢;`std::unordered_map`快但无序。 - 合理选择容器影响整体性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值