Java并发第一性原理:为什么并发如此困难?

引子:一个秒杀系统的困局 假设你正在开发一个电商秒杀系统,库存100件商品,瞬间涌入1000个并发请求。看似简单的需求,却隐藏着并发编程最本质的困难。 场景A:单线程实现(简单但性能差) /** * 单线程秒杀服务 * 优势:简单、可预测、无并发问题 * 劣势:性能差,无法处理高并发 */ public class SingleThreadSeckillService { private int stock = 100; // 库存 public synchronized boolean seckill(String userId) { // 检查库存 if (stock <= 0) { System.out.println("库存不足,秒杀失败"); return false; } // 模拟业务处理(数据库操作、支付调用等) try { Thread.sleep(10); // 10ms的业务处理时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 扣减库存 stock--; System.out.println("用户 " + userId + " 秒杀成功,剩余库存:" + stock); return true; } } // 性能测试 public class SingleThreadTest { public static void main(String[] args) { SingleThreadSeckillService service = new SingleThreadSeckillService(); long start = System.currentTimeMillis(); // 1000个用户顺序秒杀 for (int i = 0; i < 1000; i++) { service.seckill("User-" + i); } long end = System.currentTimeMillis(); System.out.println("总耗时:" + (end - start) + "ms"); } } /* 执行结果: 总耗时:10,000ms(10秒) 分析:每个请求10ms,1000个请求串行执行,总计10秒 问题:在真实秒杀场景中,10秒是不可接受的响应时间 */ 性能瓶颈分析: ...

2025-11-03 · maneng

线程安全与同步机制:从synchronized到Lock的演进

引子:一个购物车的线程安全之路 在上一篇文章中,我们理解了并发编程的三大核心问题:可见性、原子性、有序性。现在我们要解决这个问题:如何让多线程安全地访问共享数据? 场景:电商购物车的并发问题 /** * 购物车服务(线程不安全版本) * 问题:多个线程同时添加商品,可能导致数据丢失 */ public class ShoppingCart { private Map<String, Integer> items = new HashMap<>(); // 商品ID → 数量 // 添加商品到购物车 public void addItem(String productId, int quantity) { Integer currentQty = items.get(productId); if (currentQty == null) { items.put(productId, quantity); } else { items.put(productId, currentQty + quantity); } } // 获取购物车总商品数 public int getTotalItems() { int total = 0; for (Integer qty : items.values()) { total += qty; } return total; } } // 并发测试 public class CartTest { public static void main(String[] args) throws InterruptedException { ShoppingCart cart = new ShoppingCart(); // 10个线程并发添加商品 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 100; j++) { cart.addItem("product-123", 1); } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); System.out.println("期望数量:1000"); System.out.println("实际数量:" + cart.getTotalItems()); } } /* 执行结果(多次运行不一致): 第1次:实际数量:856 ❌ 丢失144次更新 第2次:实际数量:923 ❌ 丢失77次更新 第3次:实际数量:891 ❌ 丢失109次更新 问题分析: 1. HashMap本身不是线程安全的 2. addItem方法不是原子操作(读取、计算、写入三步) 3. 多线程并发执行导致数据竞争 解决方案演进: Level 1:使用synchronized → 简单但粗粒度 Level 2:使用ReentrantLock → 灵活但需要手动管理 Level 3:使用ConcurrentHashMap → 高性能(下一篇讲解) */ 本文将深入探讨如何通过synchronized和Lock来保证线程安全。 ...

2025-11-03 · maneng

并发工具类与原子操作:无锁编程的艺术

引子:一个计数器的性能优化之路 在前两篇文章中,我们学习了synchronized和Lock来保证线程安全。但是,锁总是有性能开销的。有没有不用锁就能保证线程安全的方法?答案是:CAS(Compare-And-Swap)+ 原子类。 场景:网站访问计数器 /** * 方案1:使用synchronized(加锁) */ public class SynchronizedCounter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } /** * 方案2:使用AtomicInteger(无锁) */ public class AtomicCounter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); // 无锁,基于CAS } public int getCount() { return count.get(); } } // 性能对比测试 public class CounterBenchmark { private static final int THREAD_COUNT = 100; private static final int ITERATIONS = 10000; public static void main(String[] args) throws InterruptedException { // 测试synchronized版本 SynchronizedCounter syncCounter = new SynchronizedCounter(); long syncTime = testCounter(() -> syncCounter.increment()); System.out.println("Synchronized耗时:" + syncTime + "ms"); // 测试AtomicInteger版本 AtomicCounter atomicCounter = new AtomicCounter(); long atomicTime = testCounter(() -> atomicCounter.increment()); System.out.println("AtomicInteger耗时:" + atomicTime + "ms"); System.out.println("性能提升:" + (syncTime * 100.0 / atomicTime - 100) + "%"); } private static long testCounter(Runnable task) throws InterruptedException { Thread[] threads = new Thread[THREAD_COUNT]; long start = System.currentTimeMillis(); for (int i = 0; i < THREAD_COUNT; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < ITERATIONS; j++) { task.run(); } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); return System.currentTimeMillis() - start; } } /* 执行结果(JDK 8, 4核CPU): Synchronized耗时:856ms AtomicInteger耗时:342ms 性能提升:150% 为什么AtomicInteger更快? 1. 无锁,避免线程阻塞和上下文切换 2. 基于CPU的CAS指令,硬件级支持 3. 自旋重试,适合竞争不激烈的场景 但是,AtomicInteger不是万能的: ├─ 竞争激烈时,自旋会浪费CPU ├─ 不适合复杂的原子操作 └─ 需要理解CAS的原理和限制 */ 本文将深入探讨CAS的原理、Atomic类的实现、以及常用的并发工具类。 ...

2025-11-03 · maneng

线程池与异步编程:从Thread到CompletableFuture的演进

引子:一个Web服务器的性能优化之路 假设你正在开发一个Web服务器,每个HTTP请求需要启动一个新线程来处理。看似简单的设计,却隐藏着严重的性能问题。 场景A:为每个请求创建新线程 /** * 方案1:为每个请求创建新线程(性能差) */ public class ThreadPerRequestServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("服务器启动,监听8080端口"); while (true) { Socket socket = serverSocket.accept(); // 为每个请求创建新线程 new Thread(() -> { try { handleRequest(socket); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } } private static void handleRequest(Socket socket) throws IOException { // 模拟请求处理(读取请求、业务处理、返回响应) InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); // 简化处理 byte[] buffer = new byte[1024]; in.read(buffer); String response = "HTTP/1.1 200 OK\r\n\r\nHello World"; out.write(response.getBytes()); } } /* 性能测试(使用JMeter压测): 并发数:1000 请求总数:10000 结果: ├─ 吞吐量:500 req/s ├─ 平均响应时间:2000ms ├─ CPU使用率:60% └─ 内存使用:峰值2GB 问题分析: 1. 线程创建开销大 ├─ 每个线程需要1MB栈空间 ├─ 1000个线程 = 1GB内存 └─ 线程创建/销毁耗时(ms级别) 2. 上下文切换频繁 ├─ 1000个线程竞争CPU ├─ 大量时间花在线程切换 └─ CPU利用率低 3. 系统资源耗尽 ├─ 线程数无限制 ├─ 可能导致OOM └─ 系统崩溃 */ 场景B:使用线程池 /** * 方案2:使用线程池(性能好) */ public class ThreadPoolServer { // 创建固定大小的线程池 private static final ExecutorService executor = Executors.newFixedThreadPool(100); public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("服务器启动,监听8080端口"); while (true) { Socket socket = serverSocket.accept(); // 提交任务到线程池 executor.submit(() -> { try { handleRequest(socket); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }); } } private static void handleRequest(Socket socket) throws IOException { // 同上 InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); byte[] buffer = new byte[1024]; in.read(buffer); String response = "HTTP/1.1 200 OK\r\n\r\nHello World"; out.write(response.getBytes()); } } /* 性能测试(同样的压测条件): 并发数:1000 请求总数:10000 结果: ├─ 吞吐量:5000 req/s ← 提升10倍! ├─ 平均响应时间:200ms ← 降低10倍! ├─ CPU使用率:85% ← 提升25% └─ 内存使用:峰值200MB ← 降低10倍! 优势分析: 1. 线程复用 ├─ 100个线程处理1000个请求 ├─ 无需频繁创建/销毁线程 └─ 节省大量时间和内存 2. 减少上下文切换 ├─ 线程数固定(100个) ├─ 上下文切换次数大幅减少 └─ CPU利用率提升 3. 资源可控 ├─ 线程数有上限 ├─ 内存使用可预测 └─ 系统稳定 */ 性能对比总结 对比维度 直接创建线程 线程池 提升 吞吐量 500 req/s 5000 req/s 10倍 响应时间 2000ms 200ms 10倍 CPU使用率 60% 85% +25% 内存峰值 2GB 200MB 10倍 线程数 1000+ 100 可控 核心洞察: ...

2025-11-03 · maneng

并发集合:从HashMap到ConcurrentHashMap的演进

引子:一个缓存系统的并发灾难 假设你正在开发一个高并发的缓存系统,使用HashMap存储缓存数据。看似简单的设计,却可能导致系统崩溃。 场景:本地缓存的并发问题 /** * 方案1:使用HashMap(线程不安全) */ public class UnsafeCache { private Map<String, String> cache = new HashMap<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } public static void main(String[] args) throws InterruptedException { UnsafeCache cache = new UnsafeCache(); // 10个线程并发写入 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { cache.put("key-" + threadId + "-" + j, "value-" + j); } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); System.out.println("缓存大小:" + cache.cache.size()); System.out.println("期望大小:10000"); } } /* 执行结果(多次运行): 第1次:缓存大小:9856 ❌ 数据丢失 第2次:缓存大小:9923 ❌ 数据丢失 第3次:程序卡死 ❌ 死循环(JDK 1.7) 第4次:抛出ConcurrentModificationException 问题列表: 1. 数据丢失(最常见) └─ 多个线程同时put,覆盖彼此的修改 2. 死循环(JDK 1.7的经典Bug) └─ 扩容时形成环形链表,get()陷入死循环 3. ConcurrentModificationException └─ 迭代时其他线程修改了HashMap 4. 数据不一致 └─ size()返回错误的值 真实案例: 某公司生产环境,使用HashMap作为本地缓存 高峰期CPU飙到100%,经排查是HashMap死循环 导致服务不可用,损失数百万 */ 解决方案演进 /** * 方案2:使用Hashtable(线程安全,但性能差) */ public class HashtableCache { private Map<String, String> cache = new Hashtable<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } } /* Hashtable的问题: 1. 整个方法都加synchronized(粗粒度锁) 2. 读写都加锁(读也需要互斥) 3. 性能差(高并发场景) 性能测试: 并发读写10000次 ├─ HashMap(不安全):50ms ├─ Hashtable(安全):500ms └─ 性能下降10倍! */ /** * 方案3:使用Collections.synchronizedMap(性能同样差) */ public class SynchronizedMapCache { private Map<String, String> cache = Collections.synchronizedMap(new HashMap<>()); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } } /* Collections.synchronizedMap的实现: public V put(K key, V value) { synchronized (mutex) { // 全局锁 return m.put(key, value); } } 问题:与Hashtable相同,粗粒度锁 */ /** * 方案4:使用ConcurrentHashMap(最佳方案) */ public class ConcurrentHashMapCache { private Map<String, String> cache = new ConcurrentHashMap<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } public static void main(String[] args) throws InterruptedException { ConcurrentHashMapCache cache = new ConcurrentHashMapCache(); // 10个线程并发写入 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { cache.put("key-" + threadId + "-" + j, "value-" + j); } }); } long start = System.currentTimeMillis(); for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); long end = System.currentTimeMillis(); System.out.println("缓存大小:" + cache.cache.size()); System.out.println("期望大小:10000"); System.out.println("耗时:" + (end - start) + "ms"); } } /* 执行结果: 缓存大小:10000 ✅ 正确 期望大小:10000 耗时:65ms ConcurrentHashMap的优势: 1. 线程安全 2. 性能好(细粒度锁/无锁) 3. 读操作无锁(大部分情况) 4. 不会死循环 */ 性能对比总结 方案 线程安全 读性能 写性能 适用场景 HashMap ❌ 极快 极快 单线程 Hashtable ✅ 慢(加锁) 慢(加锁) 低并发 SynchronizedMap ✅ 慢(加锁) 慢(加锁) 低并发 ConcurrentHashMap ✅ 快(无锁) 较快(细粒度锁) 高并发 核心洞察: ...

2025-11-03 · maneng

并发设计模式与最佳实践

一、不可变对象模式 1.1 不可变对象的设计 /** * 标准的不可变对象设计 */ public final class ImmutableUser { private final String name; private final int age; private final List<String> hobbies; public ImmutableUser(String name, int age, List<String> hobbies) { this.name = name; this.age = age; // 防御性复制 this.hobbies = new ArrayList<>(hobbies); } public String getName() { return name; } public int getAge() { return age; } public List<String> getHobbies() { // 返回不可变视图 return Collections.unmodifiableList(hobbies); } } /* 不可变对象的五大要求: 1. ✅ 类声明为final 2. ✅ 所有字段都是final 3. ✅ 所有字段都是private 4. ✅ 不提供setter方法 5. ✅ 可变字段防御性复制 优势: ├─ 天然线程安全 ├─ 可以安全共享 └─ 适合做缓存key 适用场景:String、Integer、LocalDate等 */ 二、ThreadLocal模式 2.1 基本使用 /** * ThreadLocal:线程本地存储 */ public class ThreadLocalExample { // 每个线程独立的SimpleDateFormat private static ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public static String formatDate(Date date) { return dateFormat.get().format(date); // 线程安全 } public static void main(String[] args) { // 10个线程并发格式化日期 for (int i = 0; i < 10; i++) { new Thread(() -> { System.out.println(formatDate(new Date())); }).start(); } } } /* 使用场景: 1. SimpleDateFormat(线程不安全) 2. 数据库连接(每个线程独立连接) 3. 用户上下文(Spring Security) */ 2.2 内存泄漏问题 /** * ThreadLocal内存泄漏问题 */ public class ThreadLocalMemoryLeak { private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { // 线程池场景 ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { executor.submit(() -> { // 存储大对象 threadLocal.set(new byte[1024 * 1024]); // 1MB // 业务逻辑 // ... // ❌ 忘记清理,导致内存泄漏 // threadLocal.remove(); // 正确做法 }); } executor.shutdown(); } /* 内存泄漏原因: 1. ThreadLocal存储在ThreadLocalMap中 2. key是弱引用(ThreadLocal) 3. value是强引用(实际对象) 4. 线程池线程复用,ThreadLocalMap一直存在 5. value无法被GC,导致内存泄漏 解决方案: try { threadLocal.set(value); // 业务逻辑 } finally { threadLocal.remove(); // 必须清理 } */ } 三、两阶段终止模式 3.1 优雅停止线程 /** * 正确的线程停止方式 */ public class GracefulShutdown { private volatile boolean running = true; public void start() { Thread thread = new Thread(() -> { while (running) { // 检查标志位 try { // 业务逻辑 Thread.sleep(1000); } catch (InterruptedException e) { // 响应中断 Thread.currentThread().interrupt(); break; } } // 清理资源 cleanup(); }); thread.start(); } public void stop() { running = false; // 第一阶段:设置标志位 } private void cleanup() { // 第二阶段:清理资源 System.out.println("清理资源"); } /* 错误做法: thread.stop(); // ❌ 已废弃,不安全 正确做法: 1. volatile标志位 2. interrupt()中断 3. 两阶段:设置标志 + 清理资源 */ } 四、读写锁模式 4.1 ReentrantReadWriteLock /** * 读写锁:读多写少场景 */ public class ReadWriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Map<String, String> cache = new HashMap<>(); // 读操作:共享锁 public String get(String key) { rwLock.readLock().lock(); try { return cache.get(key); } finally { rwLock.readLock().unlock(); } } // 写操作:排他锁 public void put(String key, String value) { rwLock.writeLock().lock(); try { cache.put(key, value); } finally { rwLock.writeLock().unlock(); } } /* 读写锁特性: ├─ 读-读:可并发 ├─ 读-写:互斥 └─ 写-写:互斥 性能提升: 读多写少场景,性能比synchronized高3-5倍 */ } 4.2 StampedLock(JDK 8) /** * StampedLock:性能更好的读写锁 */ public class StampedLockExample { private final StampedLock lock = new StampedLock(); private double x, y; // 乐观读 public double distanceFromOrigin() { long stamp = lock.tryOptimisticRead(); // 乐观读 double currentX = x; double currentY = y; if (!lock.validate(stamp)) { // 验证是否被修改 stamp = lock.readLock(); // 升级为悲观读锁 try { currentX = x; currentY = y; } finally { lock.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } // 写锁 public void move(double deltaX, double deltaY) { long stamp = lock.writeLock(); try { x += deltaX; y += deltaY; } finally { lock.unlockWrite(stamp); } } /* StampedLock优势: 1. 乐观读不加锁(性能极高) 2. 读写性能比ReentrantReadWriteLock高 3. 适合读多写少场景 注意: ❌ 不支持重入 ❌ 不支持Condition */ } 五、并发编程最佳实践 5.1 核心原则 1. 优先使用不可变对象 └─ String、Integer、LocalDate 2. 减少锁的范围 └─ 只锁必要的代码 3. 避免锁嵌套 └─ 容易死锁 4. 使用并发工具类 ├─ AtomicInteger替代synchronized计数 ├─ ConcurrentHashMap替代Hashtable ├─ CountDownLatch/CyclicBarrier协调线程 └─ BlockingQueue实现生产者-消费者 5. 线程池管理 ├─ 自定义ThreadPoolExecutor(禁用Executors) ├─ 优雅关闭(shutdown + awaitTermination) └─ 线程命名(便于排查) 6. 异常处理 ├─ 捕获InterruptedException ├─ 设置UncaughtExceptionHandler └─ 避免异常导致线程死亡 5.2 常见陷阱 /** * 常见并发陷阱 */ public class ConcurrencyTraps { // ❌ 陷阱1:双重检查锁定错误 private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 需要volatile } } } return instance; } // ✅ 正确:private static volatile Singleton instance; // ❌ 陷阱2:SimpleDateFormat共享 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // ✅ 正确:使用ThreadLocal或DateTimeFormatter // ❌ 陷阱3:复合操作不原子 if (!map.containsKey(key)) { map.put(key, value); } // ✅ 正确:map.putIfAbsent(key, value); // ❌ 陷阱4:忘记释放锁 lock.lock(); // 业务逻辑 lock.unlock(); // 异常时不会执行 // ✅ 正确:try-finally // ❌ 陷阱5:使用Executors工厂方法 Executors.newFixedThreadPool(10); // 无界队列 // ✅ 正确:自定义ThreadPoolExecutor static class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { return instance; } } } 5.3 性能调优 1. 减少锁竞争 ├─ 缩小锁范围 ├─ 降低锁粒度(分段锁) └─ 使用无锁算法(CAS) 2. 减少上下文切换 ├─ 控制线程数(CPU核心数 × 2) ├─ 使用协程/虚拟线程(JDK 19+) └─ 避免频繁阻塞 3. 内存优化 ├─ 对象池(避免频繁创建) ├─ ThreadLocal注意清理 └─ 合理设置JVM参数 4. 监控与调优 ├─ JProfiler监控线程状态 ├─ Arthas诊断死锁 └─ 压测验证性能 六、总结:并发编程知识体系 6.1 核心知识点回顾 第1篇:并发三大问题 ├─ 可见性:CPU缓存导致 ├─ 原子性:指令交错执行 ├─ 有序性:指令重排序 └─ JMM:happens-before规则 第2篇:同步机制 ├─ synchronized:锁升级(偏向锁→轻量级锁→重量级锁) ├─ Lock:ReentrantLock、公平锁/非公平锁 ├─ AQS:CLH队列、独占/共享模式 └─ 死锁:四个必要条件、预防策略 第3篇:无锁编程 ├─ CAS:CPU的CMPXCHG指令 ├─ Atomic:AtomicInteger、LongAdder ├─ 并发工具:CountDownLatch、Semaphore └─ 无锁数据结构:无锁栈、无锁队列 第4篇:线程池与异步 ├─ ThreadPoolExecutor:七参数、四拒绝策略 ├─ ForkJoinPool:工作窃取算法 ├─ CompletableFuture:组合式异步编程 └─ 最佳实践:配置、监控、优雅关闭 第5篇:并发集合 ├─ ConcurrentHashMap:分段锁→CAS+synchronized ├─ BlockingQueue:生产者-消费者模式 ├─ CopyOnWriteArrayList:写时复制 └─ 选择指南:性能对比、适用场景 第6篇:设计模式与实践 ├─ 不可变对象:天然线程安全 ├─ ThreadLocal:线程本地存储 ├─ 读写锁:读多写少优化 └─ 最佳实践:避坑指南、性能调优 6.2 学习路径建议 阶段1:理解原理(1-2周) ├─ JMM、happens-before ├─ 三大问题 └─ volatile、final 阶段2:掌握同步(2-3周) ├─ synchronized原理 ├─ Lock接口 └─ AQS源码 阶段3:无锁编程(2-3周) ├─ CAS原理 ├─ Atomic类 └─ 并发工具类 阶段4:工程实践(4-6周) ├─ 线程池配置 ├─ 并发集合使用 ├─ 实际项目应用 └─ 性能调优 总计:约3个月系统学习 6.3 推荐资源 经典书籍: 1. 《Java并发编程实战》 ⭐⭐⭐⭐⭐ 2. 《Java并发编程的艺术》 ⭐⭐⭐⭐ 3. 《深入理解Java虚拟机》 ⭐⭐⭐⭐⭐ 官方文档: 1. JDK源码(java.util.concurrent包) 2. Java Language Specification - Chapter 17 3. JSR-133: Java Memory Model 开源项目: 1. Netty(高性能网络框架) 2. Disruptor(高性能队列) 3. Spring(DI容器、事务管理) 结语 Java并发编程系列完结 ...

2025-11-03 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...