墨白吖
努力脱离外包
·昨天 23:02
昨天的答案,如果有路过好心人看到是不对的欢迎批评指正 ## 一、CAS 是什么? ### 答案 CAS 的全称是 Compare-And-Swap,也就是比较并交换。 它的核心思路是:先比较内存中的当前值和预期值是否一致。如果一致,就把它更新成新的值;如果不一致,说明当前值可能已经被其他线程修改了,这次更新就会失败。 Java 中很多原子类,比如 AtomicInteger,底层就会使用 CAS 实现原子更新操作。它的优点是,在合适的场景下,可以不通过传统的加锁方式来完成更新。 不过,CAS 也有一定的局限,比如 ABA 问题、高并发竞争下反复重试会消耗 CPU,而且它不能直接保证多个独立变量一起更新的原子性。 ------ ## 二、CAS 会有什么问题? ### 答案 我理解 CAS 主要有三个问题。 第一个是 **ABA 问题**。比如一个变量原来是 A,后来被修改成 B,又变回了 A。CAS 只比较当前值和预期值,如果发现当前值又是 A,就可能认为它没有发生变化,但实际上它中间已经被修改过了。可以使用 AtomicStampedReference,通过增加版本戳来辅助解决这个问题。 第二个是 **自旋重试带来的 CPU 开销**。如果多个线程同时竞争一个变量,CAS 失败后不断重试,就会消耗额外的 CPU 资源。 第三个是 **原子性范围的问题**。CAS 本身可以保证一次比较和更新操作的原子性,但不能直接保证多个独立变量作为一个整体更新的原子性。 如果业务需要同时修改多个字段,可以考虑使用 synchronized、Lock,或者把多个字段封装成一个不可变对象,再通过 AtomicReference 原子地替换整个对象引用。 **CAS 本身是原子的,但不代表整个业务操作都是原子的。** ------ ## 三、CountDownLatch 是干什么的? ### 答案 CountDownLatch 是 JUC 并发工具包中的一个同步工具,主要用来协调多个线程之间的执行顺序。 比如,主线程启动了三个任务,希望等这三个任务全部执行完成之后,再继续往下执行,就可以使用 CountDownLatch。 创建的时候,需要传入一个初始计数,例如 `new CountDownLatch(3)`。每个任务完成后调用 `countDown()`,让计数减一;需要等待的线程调用 `await()`。当计数减到零时,等待的线程就可以继续执行了。 它比较适合等待多个并发任务完成的场景。不过,它是一次性的,计数归零以后不能重新设置计数继续使用。 ------ ## 四、Java 原子操作类都有哪些? ### 答案 Java 在 `java.util.concurrent.atomic` 包下面提供了很多原子操作类。 比较常用的有四种: - AtomicInteger:原子更新整数。 - AtomicLong:原子更新长整数。 - AtomicBoolean:原子更新布尔值。 - AtomicReference:原子更新对象引用。 除此之外,还有数组类型的原子类,比如 AtomicIntegerArray,以及带版本戳的 AtomicStampedReference。 以 AtomicInteger 为例,它提供了 `getAndIncrement()`、`incrementAndGet()` 和 `compareAndSet()` 等方法,可以在并发环境下进行原子更新。 其中,`getAndIncrement()` 是先返回旧值,再加一;`incrementAndGet()` 是先加一,再返回新值。 如果需要把多个字段作为一个整体进行更新,也可以考虑将它们封装成一个不可变对象,再通过 AtomicReference 原子地替换对象引用。 ------ ## 五、什么是 RAG? ### 答案 RAG 的全称是 Retrieval-Augmented Generation,中文叫检索增强生成。 我理解它主要是为了解决大模型无法直接获取企业内部私有资料,以及难以及时掌握最新业务信息的问题。 比如,我们要做一个企业知识库问答系统。当用户提出一个公司内部的业务问题时,系统会先从知识库中检索相关的文档片段,再把这些片段和用户的问题一起交给大模型,让大模型结合检索到的资料生成回答。 所以,RAG 的核心就是**先检索,再生成**。 这样可以让大模型在回答问题时参考企业自己的资料,也可以返回相关的文档来源,方便用户核对。 不过,RAG 并不能保证回答一定正确,最终效果还取决于文档解析、切片质量、检索效果以及大模型的生成能力。 另外,RAG 通常不需要重新训练大模型,而是在回答问题时动态提供外部知识。**微调主要是调整模型参数,RAG 主要是让模型在回答时参考外部检索到的知识。** ------ ## 六、RAG 的完整工作流程是什么? ### 答案 我一般会把 RAG 的工作流程分成两个阶段:**离线建库和在线问答**。 首先是离线建库阶段。 我们先读取不同格式的文档,比如 PDF、Word、Markdown 等,然后进行文档解析和内容清洗,再按照一定的策略把文档切分成多个文本片段。 接着,使用 Embedding 模型把这些文本片段转换成向量,最后将向量、原始文本和相关元数据一起存储到向量数据库中。 然后是在线问答阶段。 用户提出问题后,系统可以根据需要对问题进行改写,再将问题转换成向量,到向量数据库中检索相关的文本片段。 为了进一步提高检索准确性,还可以使用 Rerank 模型对初步检索结果重新排序,选出更相关的内容。 最后,把用户的问题和检索到的文本片段一起组织成 Prompt,交给大模型生成答案。如果业务有要求,还可以返回引用来源。 所以整体可以概括为:**离线解析文档、切片、向量化和存储;在线处理问题、检索相关内容,再增强生成。** ------ ## 七、RAG 的文档切分策略有哪些? ### 答案 文档切分是 RAG 中比较重要的一步,因为切分质量会直接影响后续的检索效果。 我了解的常见策略主要有几种。 第一种是**固定大小切分**,可以按照字符数或者 Token 数进行切分。这种方式实现比较简单,但是如果直接硬切,可能会把一句话或者一段完整的意思切断,所以通常会设置一定的重叠区域,也就是 Overlap。 第二种是**按照文档结构或语义边界切分**,比如优先按照标题、段落、句子进行切分,尽量让每个片段表达相对完整的意思。 第三种是**针对特殊内容进行切分**。比如代码可以按照类、方法或者函数进行切分;表格则要尽量保留表头和行列之间的对应关系。 另外,还有父子切分策略。可以用较小的片段进行向量检索,提高定位的精度,再根据关联关系取出包含更多上下文的较大片段交给大模型。 根据文档类型、检索效果和模型上下文长度进行选择。 ------ ## 八、@Autowired 和 @Resource 有什么区别? ### 答案 这两个注解都可以用来实现依赖注入,主要区别在于来源和默认的依赖查找方式。 @Autowired 是 Spring 提供的注解,默认主要按照类型匹配 Bean。如果同一个类型有多个 Bean,就可以使用 @Qualifier 指定具体要注入哪一个。 @Resource 来自 Java 的通用注解规范,在现代项目中通常使用 `javax.annotation.Resource` 或 `jakarta.annotation.Resource`,具体取决于项目版本。 在 Spring 中,@Resource 通常优先按照名称查找 Bean,也可以通过 `name` 属性明确指定要注入的 Bean。 ------ ## 九、Spring 中有哪些常见的设计模式? ### 答案 Spring 里面使用了不少设计模式,我比较熟悉的有以下几种。 第一种是**单例模式**。Spring 中的 Bean 默认使用 singleton 作用域,同一个 IoC 容器中的同一个 Bean 定义通常只有一个实例。 第二种是**工厂模式**。Spring 容器可以通过 BeanFactory、ApplicationContext 等机制创建和管理 Bean,我们不需要在业务代码里到处手动创建对象。 第三种是**代理模式**。比如 Spring AOP 可以通过代理来实现事务、日志等横切功能,让业务代码和这些通用逻辑分离。 第四种是**模板方法模式**。比如 JdbcTemplate 把一些重复的数据库操作流程封装起来,让我们更多地关注具体的 SQL 和业务逻辑。 第五种是**观察者模式**。Spring 的事件发布和监听机制就体现了这种模式,一个组件发布事件,其他监听器可以接收并处理事件。 这些设计模式的目的都是让代码职责更清晰,降低耦合,提高复用性和扩展性。 需要注意的是,Spring Bean 默认是单例,并不代表所有 Bean 永远都是单例。Bean 也可以配置成其他作用域,比如 prototype、request 和 session。 ------ ## 十、什么是 IoC? ### 答案 IoC 的全称是 Inversion of Control,也就是控制反转。 我理解它的核心就是把对象的创建和依赖关系管理交给容器,而不是完全由业务代码自己控制。 比如,传统开发中,一个 Service 需要使用另一个对象,可能会自己通过 `new` 创建它。这样对象之间容易产生比较强的耦合。 使用 Spring IoC 之后,我们可以把类交给 Spring 容器管理,由容器负责创建 Bean、注入依赖,并按照相应的配置管理 Bean 的生命周期。 这样,如果以后需要替换某个依赖的实现,通常可以通过调整注入关系或相关配置来完成,而不需要在业务代码中到处修改对象的创建逻辑。 Spring 中主要通过依赖注入,也就是 DI,来实现 IoC。 所以可以这样理解:**IoC 是一种设计思想,DI 是实现这种思想的主要方式之一。**
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP