【深度解析】Java高级并发模式与实践:从ThreadLocal到无锁编程,全面避坑指南!

🔍 一、ThreadLocal:线程隔离的利器与内存泄露陷阱

底层原理揭秘:
每个线程内部维护ThreadLocalMap,Key为弱引用的ThreadLocal对象,Value为存储的值。这种设计导致了经典内存泄露场景:

// 典型应用:用户会话存储
public class UserContextHolder {private static final ThreadLocal<User> currentUser = new ThreadLocal<>();public static void set(User user) {currentUser.set(user);}public static User get() {return currentUser.get();}// 关键!必须清理public static void clear() {currentUser.remove(); }
}

核心应用场景

  • 上下文信息传递:用户 Session、事务 ID、请求链路追踪 ID,避免在方法间显式传递参数。
  • 线程不安全工具类副本:如 SimpleDateFormat(替代方案是使用 ThreadLocal 或 JDK 8 的 DateTimeFormatter)。
  • 性能优化:避免重复创建对象(如数据库连接的非线程安全包装类)。

关键实践与风险

  • 内存泄露
    • 根本原因:线程池中的线程长期存活,ThreadLocal 值未被清除 → 值对象(强引用)无法回收。
    • 解决方案:
      • 使用 ThreadLocal.remove() 显式清理(如 finally 块中)。
      • 使用 WeakReferenceThreadLocalMap 的 Key 是弱引用,但 Value 仍是强引用)。
  • 最佳实践
    • 始终在 try-finally 中清理
      userContext.set(currentUser);
      try {// 业务逻辑
      } finally {userContext.remove(); // 强制清除
      }
      
    • 避免存储大对象(易引发 OOM)。

⚠️ 高并发下的特殊场景:

  • 线程池中线程复用会导致信息串用
  • InheritableThreadLocal实现父子线程传递
  • Spring框架中RequestContextHolder的实现原理

🧱 二、不变性(Immutability):并发安全的终极武器

实现方式

  1. 类用 final 修饰(防继承覆盖)。
  2. 所有字段用 final 修饰(构造后不可变)。
  3. 不暴露修改方法(如 setter)。
  4. 返回防御性拷贝(如集合类返回 Collections.unmodifiableList)。

优势

  • 天然线程安全:无竞态条件,无需同步。
  • 简化代码:无需考虑锁和并发控制。
  • 安全共享:对象可在多线程间自由传递。

经典案例

  • StringBigDecimaljava.time 包中的日期类。
  • 自定义不可变对象:
    // 典型的不可变类
    public final class ImmutableConfig {private final int timeout;private final List<String> servers; // 引用类型需特殊处理public ImmutableConfig(int timeout, List<String> servers) {this.timeout = timeout;this.servers = Collections.unmodifiableList(new ArrayList<>(servers));}// 返回不可修改的副本public List<String> getServers() {return Collections.unmodifiableList(servers);}
    }
    

性能对比实验

方案10万次读(ms)10万次写(ms)内存占用
不可变对象45-中等
synchronized210185
ConcurrentHashMap6572

⚙️ 三、六大并发设计模式实战

模式核心思想实现工具
生产者-消费者解耦生产与消费逻辑BlockingQueue (如 LinkedBlockingQueue)
线程池模式复用线程,避免频繁创建销毁开销ExecutorService (如 ThreadPoolExecutor)
工作窃取模式平衡负载,空闲线程偷取其他队列任务ForkJoinPool
主从/领导者-跟随者主线程分配任务,从线程执行并返回结果CompletableFuture + 线程池
流水线模式任务分阶段处理,类似工厂流水线PhaserCyclicBarrier
不变对象模式无变化则无需同步finalRecord类型 、不可变集合、防御性拷贝
🔥 1. 生产者-消费者模式(解耦神器)

适用场景:日志处理、消息队列、订单系统

// 实战代码示例
BlockingQueue<Log> queue = new ArrayBlockingQueue<>(100); // 有界队列防OOM// 生产者线程池
ExecutorService producers = Executors.newCachedThreadPool();
producers.execute(() -> {while (isRunning) {Log log = generateLog(); // 模拟日志生成queue.put(log);  // 队列满时自动阻塞}
});// 消费者线程池(4个消费者)
ExecutorService consumers = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {consumers.execute(() -> {while (isRunning) {Log log = queue.take(); // 队列空时阻塞uploadToES(log);  // 上传到Elasticsearch}});
}

最佳实践

  1. 使用new ArrayBlockingQueue<>(capacity)避免无界队列导致OOM
  2. 生产/消费者线程分离管理
  3. 毒丸(Poison Pill)终止策略:
    queue.put(POISON_PILL); // 发送终止信号
    // 消费者收到特殊对象时退出
    

⚙️ 2. 线程池模式(资源复用典范)

四类线程池适用场景

// 1. 固定大小线程池(CPU密集型)
ExecutorService fixedPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());// 2. 缓存线程池(短时异步任务)
ExecutorService cachedPool = Executors.newCachedThreadPool(); // 注意OOM风险!// 3. 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
scheduledPool.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);// 4. 工作窃取线程池(ForkJoinPool)
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
forkJoinPool.submit(() -> { /* 可分拆任务 */ });

自定义线程池黄金参数

ThreadPoolExecutor customPool = new ThreadPoolExecutor(4, // 核心线程数16, // 最大线程数60, TimeUnit.SECONDS, // 空闲回收new ArrayBlockingQueue<>(200), // 有界队列new CustomThreadFactory(), // 命名线程new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者执行
);

🧩 3. 工作单元模式(任务分治)

ForkJoin框架实战

// 计算1~n的平方和
class SumTask extends RecursiveTask<Long> {private final int start;private final int end;@Overrideprotected Long compute() {if (end - start <= 1000) { // 小任务直接计算long sum = 0;for (int i = start; i <= end; i++) sum += i * i;return sum;}// 大任务拆分(二分法)int mid = (start + end) / 2;SumTask leftTask = new SumTask(start, mid);SumTask rightTask = new SumTask(mid+1, end);leftTask.fork(); // 异步执行子任务return rightTask.compute() + leftTask.join(); // 合并结果}
}// 调用示例
ForkJoinPool pool = new ForkJoinPool();
long result = pool.invoke(new SumTask(1, 100_000));

场景:大数据处理、归并排序、复杂计算


👑 4. 主从/领导者-跟随者模式

分布式计算实现

// Leader节点(任务分配)
List<Future<Result>> futures = new ArrayList<>();
for (Task task : allTasks) {futures.add(executor.submit(() -> worker.execute(task)));
}// 结果聚合
List<Result> results = new ArrayList<>();
for (Future<Result> future : futures) {results.add(future.get()); // 阻塞等待所有结果
}// Worker节点示例
class Worker implements Callable<Result> {public Result call() {return process(ThreadLocalRandom.current().nextInt()); }
}

优化技巧

  1. 使用CompletionService实现完成顺序获取结果:
    CompletionService<Result> cs = new ExecutorCompletionService<>(executor);
    cs.submit(worker);
    Result r = cs.take().get(); // 获取最先完成的任务
    
  2. 批量任务拆分执行(MapReduce思想)

🧱 5. 不变对象模式(安全基石)

深度不变实现

public final class ImmutableConfig {private final int timeout;private final List<String> urls;  // 引用类型特殊处理public ImmutableConfig(int timeout, List<String> sourceUrls) {this.timeout = timeout;// 防御性复制 + 不可变包装this.urls = Collections.unmodifiableList(new ArrayList<>(sourceUrls));}// 返回不可修改的副本public List<String> getUrls() {return Collections.unmodifiableList(new ArrayList<>(urls));}// 构建器模式(可选)public static class Builder {private int timeout;private List<String> urls = new ArrayList<>();public Builder setTimeout(int t) { this.timeout = t; return this; }public Builder addUrl(String url) { urls.add(url); return this; }public ImmutableConfig build() {return new ImmutableConfig(timeout, urls);}}
}

使用场景

  • 配置参数
  • DTO数据传输
  • 并发缓存键值

🔁 6. 流水线模式(任务分段)

Phase分阶段处理器

// 三阶段处理器
public class VideoProcessPipeline {private final ExecutorService[] stages = new ExecutorService[3];public VideoProcessPipeline() {// 每阶段独立线程池stages[0] = Executors.newFixedThreadPool(2); // 解码stages[1] = Executors.newFixedThreadPool(4); // 滤镜stages[2] = Executors.newSingleThreadExecutor(); // 编码}public void process(Video video) {// Phase 1: 解码CompletableFuture<Frame> stage1 = CompletableFuture.supplyAsync(() -> decode(video), stages[0]);// Phase 2: 滤镜处理(依赖阶段1)CompletableFuture<Frame> stage2 = stage1.thenApplyAsync(frame -> applyFilters(frame), stages[1]);// Phase 3: 编码输出(依赖阶段2)stage2.thenAcceptAsync(frame -> encodeAndSave(frame), stages[2]);}
}

特点

  1. 阶段间通过队列传递数据
  2. 可独立扩展各阶段处理能力
  3. 支持复杂业务处理流程

💡 设计模式选型矩阵
场景推荐模式性能要点
任务分发生产者-消费者队列容量控制
计算密集型工作窃取(ForkJoin)任务拆分粒度
I/O密集型领导者-跟随者异步回调
配置管理不变对象防御性拷贝
流程化业务流水线阶段线程数优化
资源复用线程池拒绝策略选择

🛠 实战性能调优Tips
  1. 线程池监控:通过JMX或Spring Boot Actuator监控队列堆积
    ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
    int queueSize = pool.getQueue().size();
    
  2. 工作窃取优化:合理设置阈值避免过度拆分
    if (end - start <= THRESHOLD) {...} // 根据实验确定最佳阈值
    
  3. 流水线瓶颈定位:使用Arthas监控各阶段耗时
    trace com.example.VideoProcessPipeline applyFilters
    

🚨 避坑警告:分布式场景下使用领导者-跟随者时,必须实现Leader选举机制(如ZooKeeper Curator)!


💥 四、无锁编程:CAS原理与ABA问题深度剖析

核心:CAS (Compare-And-Swap)

  • 底层原理:CPU 原子指令(如 x86 的 CMPXCHG),比较内存值是否等于预期值,是则更新。
  • Java 实现
    • AtomicIntegerAtomicReference 等原子类。
    • Unsafe 类(底层操作,JDK 内部 API,不推荐直接使用)。

无锁数据结构示例

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // CAS 实现自增

挑战与陷阱

  • ABA问题
    值从 A→B→A,CAS 无法感知中间变化。
    解决方案AtomicStampedReference(附加版本戳)。
  • 自旋开销:竞争激烈时 CPU 空转。
  • 实现复杂度:非阻塞算法设计极其困难(如无锁队列)。

Java原子类实现揭秘

// AtomicInteger自增源码解析
public final int incrementAndGet() {int prev, next;do {prev = get(); // 当前值next = prev + 1;} while (!compareAndSet(prev, next)); // CAS自旋return next;
}

ABA问题复现

线程1读取值A
线程2修改A->B
线程2修改B->A
线程1执行CAS: 预期值A成功

解决方案对比

方案实现类优点缺点
版本戳AtomicStampedReference彻底解决ABA额外内存开销
计数器AtomicMarkableReference实现简单可能溢出
不变对象-根本解决需要重构代码

⚡ 五、高并发性能调优实战工具箱

关键指标

  • 吞吐量:单位时间处理的任务数(TPS/QPS)。
  • 延迟:单次请求响应时间(P99 值更重要)。
  • CPU利用率:过高可能由锁竞争或频繁 GC 引起。

监控工具链

  1. 线程分析
    • jstack:获取线程栈,检测死锁(输出中搜索 deadlock)。
    • jcmd <PID> Thread.print:替代 jstack
  2. 运行时监控
    • JVisualVM/JConsole:图形化查看线程状态、锁等待。
    • Java Mission Control (JMC):低开销线上诊断。
  3. GC 诊断
    • jstat -gcutil <PID> 1000:每秒输出 GC 统计。
    • -Xlog:gc*:JDK 9+ 的详细 GC 日志。
  4. 高级诊断
    • Arthas:在线热更新、监控方法调用耗时。
    • async-profiler:低开销 CPU/内存火焰图。

优化策略

  • 减少锁竞争
    • 缩小同步范围(同步块 vs 同步方法)。
    • 分离锁(锁拆分、锁分段),如 ConcurrentHashMap 的分段锁。
    • 读写分离:ReentrantReadWriteLockStampedLock
  • 线程池调优
    • 核心参数corePoolSizemaxPoolSizeworkQueue(避免无界队列!)。
    • 拒绝策略:根据业务选 AbortPolicy(抛异常)或 CallerRunsPolicy(回退到调用线程)。
  • 非阻塞 I/O
    • 使用 Netty、Undertow 等框架,结合 NIOEventLoopGroup
  • 缓存行填充
    • 伪共享:多线程修改同一缓存行导致缓存失效。
    • 解决:@sun.misc.Contended(JDK 8+)或手动填充字段(Java 对象头占用 12-16 字节)。
      class Data {@Contended volatile long value1; // 避免伪共享
      }
      

线程池参数黄金公式

  • CPU密集型:corePoolSize = CPU核数 + 1
  • IO密集型:corePoolSize = CPU核数 * 2 + 1
  • 队列选择:
    new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(200), // 有界队列防OOMnew CustomRejectPolicy()       // 自定义拒绝策略
    );
    

🚫 六、并发陷阱与避坑指南

高频陷阱

陷阱类型解决方案/规避方案
死锁1. 固定锁顺序
2. tryLock(timeout)
3. 监控告警(如 JVM 死锁检测)
资源耗尽1. 线程池使用有界队列
2. 限制最大线程数(避免 newCachedThreadPool 滥用)
上下文切换过多减少阻塞调用,优化线程数(IO 密集型:2N+1,CPU 密集型:N+1)
不安全的发布不在构造器中暴露 this 引用(避免回调导致访问未初始化对象)
线程中断忽略正确处理 InterruptedException(恢复中断状态 Thread.currentThread().interrupt()
过度同步优先用并发容器(ConcurrentHashMap)代替手动同步 Collections.synchronizedMap
  1. 死锁四大条件破局

    if (lock1.tryLock(100, MILLISECONDS)) {try {if (lock2.tryLock(100, MILLISECONDS)) {try {// 业务逻辑} finally { lock2.unlock(); }}} finally { lock1.unlock(); }
    }
    
  2. 线程中断的正确处理

    try {while (!Thread.interrupted()) {// 可中断操作}
    } catch (InterruptedException e) {Thread.currentThread().interrupt(); // 恢复中断状态
    }
    
  3. 资源泄漏防范清单

    • 线程池必须用shutdownNow()强制关闭
    • 数据库连接池配合removeAbandonedTimeout
    • 文件操作必须放在try-with-resources
  4. 上下文切换优化技巧

    • Linux服务器设置/proc/sys/kernel/sched_child_runs_first
    • 禁用-XX:+UseSpinning自旋锁
    • NUMA架构绑定CPU核心

黄金法则

  1. KISS原则:优先使用 java.util.concurrent 内置工具,避免重复造轮子。
  2. 理解业务并发模型:区分 CPU 密集型 vs IO 密集型任务,针对性优化。
  3. 防御式编程:对共享状态持有高度警惕,默认设计为不可变对象。
  4. 监控先行:高并发系统需配备实时监控(线程池队列积压、GC 暂停时间)。

💎 总结:并发编程三大黄金原则

  1. 优先不变性:80%的并发问题可通过不可变对象解决
  2. 工具优于造轮子java.util.concurrent覆盖90%场景
  3. 监控大于优化:没有Metrics的调优就是耍流氓

实战建议:在压测环境中,使用-XX:+PreserveFramePointer参数配合async-profiler,能精准定位锁竞争热点!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/85026.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用存储型 XSS 窃取 cookie 并发送到你控制的服务器

&#x1f9ea; 第一步&#xff1a;准备监听服务接收 cookie 在你的本机&#xff08;非容器&#xff09;或 DVWA 所在主机运行以下 Python 监听代码&#xff0c;用于接收窃取的 cookie&#xff1a; 启动 HTTP 接收服务 # 在本机终端运行&#xff0c;监听 8081 端口&#xff0…

WebDebugX和多工具组合的移动端调试流程构建:一个混合App项目的实践案例

前段时间参与了一个跨平台的医疗服务 App 项目&#xff0c;整体架构采用 Flutter 封装原生模块&#xff0c;部分功能模块嵌套 WebView 加载 H5 页面。开发过程中我们频繁遇到 Web 页面在移动端表现异常的问题&#xff0c;比如样式错乱、请求失败、性能延迟等&#xff0c;而这些…

图形编辑器基于Paper.js教程29:基于图层的所有矢量图元的填充规则实现

背景 在lightburn中&#xff0c;对于填充图层&#xff0c;有这样一个隐藏的逻辑&#xff0c;那就是&#xff0c;在加工时&#xff0c;填充规则是以填充图层的所有元素进行计算的&#xff0c;什么意思那&#xff1f; 如果你在填充图层中画了两个图形&#xff0c;一个圆&#xf…

Python 函数实战指南:提升编程效率的实用技巧

在 Python 编程的世界里&#xff0c;函数是构建高效代码的基石。掌握实用的函数技巧不仅能让代码更加简洁优雅&#xff0c;还能显著提升开发效率。我们一起将结合实际案例&#xff0c;深入剖析 Python 函数的使用技巧&#xff0c;帮助开发者在日常开发中事半功倍。 一、基础函数…

OPenCV CUDA模块图形变换----构建透视变换映射表函数buildWarpPerspectiveMaps()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数用于构建一个透视变换&#xff08;Perspective Transform&#xff09;的映射表&#xff08;xmap / ymap&#xff09;&#xff0c;可用于后…

tcping工具使用指南

tcping是一个用于测试TCP端口连通性的工具&#xff0c;它类似于传统的ping命令&#xff0c;但工作在传输层(TCP)而不是网络层(ICMP)。 基本功能 tcping的主要功能包括&#xff1a; 测试目标主机特定TCP端口是否开放 测量TCP连接建立时间 统计丢包率和响应时间 安装方法 …

CSP 2024 入门级第一轮(88.5)

4. 以下哪个序列对应数字 00 至 88 的 44 位二进制格雷码&#xff08;Gray code&#xff09;&#xff1f;&#xff08; &#xff09; A. 0000, 0001, 0011, 0010, 0110, 0111, 0101, 1000 B. 0000, 0001, 0011, 0010, 0110, 0111, 0100, 0101 C. 0000, 0001, 0011, 0010, …

三菱FX-5U系列入门到精通

第2章 中间继电器 继电器工作模式:线圈得电,常开触点闭合,常闭触点断开。总结:中间继电器线圈电压分为:24VDC 110VAC 220VAC 380VAC PLC控制柜中常用的是24VDC比较多,而动力电柜中或者控制风机水泵的电柜中220VAC比较多。大部分选择24VDC,然后用触点控制220或者380,说白…

简历模板1——王明 | 高级数据挖掘工程师 | 5年经验

王明 | 高级数据挖掘工程师 | 5年经验 &#x1f4f1; (86) 189-xxxx-xxxx | &#x1f4e7; wangmingemail.com | &#x1f4cd; 深圳市 &#x1f4bb; GitHub | &#x1f454; LinkedIn &#x1f4bc; 工作经历 ​科技前沿集团 | 高级数据挖掘工程师 &#x1f4c5; 2021.06 …

【JVM】- 内存模式

Java内存模型&#xff1a;JMM&#xff08;Java Memory Model&#xff09;&#xff0c;定义了一套在多线程环境下&#xff0c;读写共享数据&#xff08;成员变量、数组&#xff09;时&#xff0c;对数据的可见性&#xff0c;有序性和原子性的规则和保障。 原子性 问题分析 【问…

AQS独占模式——资源获取和释放源码分析

AQS资源获取&#xff08;独占模式&#xff09; Node节点类 static final class Node {//标记当前节点的线程在共享模式下等待。static final Node SHARED new Node();//标记当前节点的线程在独占模式下等待。static final Node EXCLUSIVE null;//waitStatus的值&#xff0c…

压测过程中TPS上不去可能是什么原因

进行性能分析 接口没有报错或者错误率低于1%&#xff0c;继续增加并发还是一样&#xff0c;这个时候需要考虑几点 1.是否触发限流&#xff0c;比如waf、Nginx等情况&#xff0c;有没有一些限流的情况&#xff0c;如果触发了限流&#xff0c;请求是没有达到后端的&#xff0c;所…

Golang 解大整数乘法

文章目录 Golang 解大整数乘法问题描述&#xff1a;LeetCode 43. 字符串相乘思路Golang 代码 Golang 解大整数乘法 在初学 C 语言的时候&#xff0c;我们一定接触过“字符串相加”或“字符串相乘”之类的问题&#xff0c;对于初学者而言&#xff0c;这类问题的难度一般来说是比…

web3-区块链的技术安全/经济安全以及去杠杆螺旋(经济稳定)

web3-区块链的技术安全/经济安全以及去杠杆螺旋&#xff08;经济稳定&#xff09; 三个基本设计问题 技术安全 在技术结构中对其进行原子级的、瞬时利用&#xff08;无风险&#xff09; 无风险&#xff0c;因为攻击者的结果还是二进制的&#xff1a; 只会是攻击成功 获利或…

Java多线程通信:wait/notify与sleep的深度剖析(时序图详解)

在Java多线程编程中&#xff0c;线程间的通信与协作是实现复杂并发逻辑的关键。wait()、notify()以及sleep()方法作为线程控制的重要工具&#xff0c;有着各自独特的使用场景与规则。本文将深入探讨wait()和notify()的协作机制&#xff0c;以及sleep()的阻塞特性&#xff0c;同…

关于使用EasyExcel、 Vue3实现导入导出功能

后端部分: 其中查询数据的服务省略 1、引用 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.3</version></dependency> 2、controller package com.rs.cphs.sys.controller;i…

机器学习中的数据准备关键技术

有效的数据准备对于构建强大的机器学习模型至关重要。本文档总结并阐述了为监督和非监督学习任务准备数据的关键技术。 1. 理解数据类型 有两种数据类型。定性数据描述对象的特征&#xff0c;而定量数据描述对象的数量。 定性&#xff08;分类&#xff09;数据 名义&#x…

深度学习——基于卷积神经网络实现食物图像分类【3】(保存最优模型)

文章目录 引言一、项目概述二、环境配置三、数据预处理3.1 数据转换设置3.2 数据集准备 四、自定义数据集类五、CNN模型架构六、训练与评估流程6.1 训练函数6.2 评估与模型保存 七、完整训练流程八、模型保存与加载8.1 保存模型8.2 加载模型 九、优化建议十、常见问题解决十一、…

《棒球百科》棒球怎么玩·棒球9号位

用最简单的方式介绍棒球的核心玩法和规则&#xff0c;完全零基础也能看懂&#xff1a; 一句话目标 进攻方&#xff1a;用球棒把球打飞&#xff0c;然后拼命跑完4个垒包&#xff08;逆时针绕一圈&#xff09;得分。 防守方&#xff1a;想尽办法让进攻方出局&#xff0c;阻止他…

语言模型是怎么工作的?通俗版原理解读!

大模型为什么能聊天、写代码、懂医学&#xff1f; 我们从四个关键模块&#xff0c;一步步拆开讲清楚 &#x1f447; ✅ 模块一&#xff1a;模型的“本事”从哪来&#xff1f;靠训练数据 别幻想它有意识&#xff0c;它的能力&#xff0c;全是“喂”出来的&#xff1a; 吃过成千…