Reactor几个重要的接口(1)

Reactor ContextView 详解:响应式上下文的只读视图

在 Reactor 响应式编程中,Context 是用于在操作符链中传递元数据(如追踪 ID、用户认证信息)的核心机制。ContextView 是 Reactor 3.4.0 引入的只读接口,它将上下文的读取能力 与写入能力 分离,使 API 更加清晰且安全。本文将逐段解析 ContextView 接口的源码,说明其设计意图、各方法的作用以及它与 Context 的关系。


一、接口概览与设计目标

java 复制代码
package reactor.util.context;

import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

import org.jspecify.annotations.Nullable;

/**
 * A read-only view of a collection of key/value pairs that is propagated between components
 * such as operators via the context protocol. Contexts are ideal to transport orthogonal
 * information such as tracing or security tokens.
 * <p>
 * {@link Context} is an immutable variant of the same key/value pairs structure which exposes
 * a write API that returns new instances on each write.
 */
public interface ContextView {
    // ... 方法列表
}

核心设计理念:

  • 只读分离 :ContextView 只提供读取方法,不提供写入。写入操作由子接口 Context 负责,并返回新实例(不可变)。
  • 不可变性:所有实现都是不可变的,保证线程安全。
  • 正交信息传递:用于传播不干扰主要业务逻辑的额外数据,如链路追踪、安全令牌等。
  • 类型安全 :支持按 Class 类型获取值,避免强制转换。

二、方法逐个解析

1. <T> T get(Object key)

java 复制代码
<T> T get(Object key);

作用 :根据给定的键获取对应的值。如果键不存在,抛出 NoSuchElementException。

使用场景:当确信键一定存在时,直接获取;否则使用带默认值的方法。

返回值:泛型方法,允许调用者直接指定期望的类型(隐式转换)。

注意 :调用者需自行保证类型安全,因为实际存储的值类型在编译时未知。若类型不匹配,会出现 ClassCastException。


2. default <T> T get(Class<T> key)

java 复制代码
default <T> T get(Class<T> key) {
    T v = get((Object) key);
    if (key.isInstance(v)) {
        return v;
    }
    throw new NoSuchElementException("Context does not contain a value of type " + key.getName());
}

作用 :使用 Class 对象作为键,并自动检查值类型是否匹配。若键不存在或值类型不匹配,抛出 NoSuchElementException。

设计意图:允许以类型作为键,避免字符串硬编码,且提供类型安全保证。

实现细节:

  • 先调用 get(Object key) 获取原始值。
  • 通过 key.isInstance(v) 检查值是否属于该类型。
  • 若匹配则返回,否则抛异常。

3. default <T> T getOrDefault(Object key, T defaultValue)

java 复制代码
default <T> @Nullable T getOrDefault(Object key, @Nullable T defaultValue) {
    if (!hasKey(key)) {
        return defaultValue;
    }
    return get(key);
}

作用:若键存在则返回对应的值,否则返回指定的默认值。

便利性 :避免重复检查 hasKey 并处理 Optional。


4. default <T> Optional<T> getOrEmpty(Object key)

java 复制代码
default <T> Optional<T> getOrEmpty(Object key) {
    if (hasKey(key)) {
        return Optional.of(get(key));
    }
    return Optional.empty();
}

作用 :将值包装在 Optional 中,若键不存在则返回 Optional.empty()。

风格选择:适合函数式编程风格的调用者,便于链式处理。


5. boolean hasKey(Object key)

java 复制代码
boolean hasKey(Object key);

作用:检查上下文中是否包含指定的键。

实现 :由具体子类实现,通常通过内部存储结构(如 Map)的 containsKey 判断。


6. default boolean isEmpty()

java 复制代码
default boolean isEmpty() {
    return size() == 0;
}

作用:判断上下文是否为空(不包含任何键值对)。

默认实现 :依赖 size() 方法,若大小为 0 则返回 true。


7. int size()

java 复制代码
int size();

作用:返回上下文中键值对的数量。

实现 :由子类提供,通常为内部存储结构的 size()。


8. Stream<Map.Entry<Object, Object>> stream()

java 复制代码
Stream<Map.Entry<Object, Object>> stream();

作用 :以 Stream 的形式返回所有键值对的视图,便于遍历、过滤、聚合等操作。

用途:

  • 调试时打印所有内容。
  • 批量检查或转换。

注意 :返回的是不可变条目(如 SimpleImmutableEntry),防止修改。


9. default void forEach(BiConsumer<Object, Object> action)

java 复制代码
default void forEach(BiConsumer<Object, Object> action) {
    stream().forEach(entry -> action.accept(entry.getKey(), entry.getValue()));
}

作用:遍历所有键值对,并对每个条目执行给定的操作。

默认实现 :基于 stream() 和 forEach 构建,简单直观。

优势 :与 Java 8 的 Map.forEach 风格一致,便于迁移。


三、ContextView 与 Context 的关系

  • Context 接口继承 ContextView :增加了写入方法 put、delete 等,且这些方法返回新的 Context 实例(不可变)。
  • 读写分离 :在方法签名中,若只需读取上下文,应使用 ContextView 作为参数类型,表明不修改上下文。
  • 实例类型 :实际运行时,ContextView 的实例都是 Context 的具体实现类(如 Context1、ContextN),但可以向上转型为只读视图。

示例:

java 复制代码
// 在自定义操作符中,只读上下文
public void doSomething(ContextView ctx) {
    String traceId = ctx.getOrEmpty("traceId").orElse("default");
}

四、在 Reactor 流中的使用

  • CoreSubscriber.currentContext() 返回 ContextView,但实际实现为 Context。
  • 操作符 transformDeferredContextual 接收 BiFunction,其中第二个参数是 ContextView。
  • Mono.deferContextual 和 Flux.deferContextual 允许订阅时访问上下文。

五、设计优势总结

  1. 清晰性:将只读与读写分离,使 API 意图明确。
  2. 安全性:不可变性确保多线程环境下无并发修改问题。
  3. 类型安全:提供按类型获取值的方法,减少强制转换错误。
  4. 灵活性:提供多种获取方式(直接、默认值、Optional、Stream),适应不同编程风格。
  5. 可组合性 :stream() 和 forEach 支持批量操作和函数式处理。

六、注意事项

  • 键值不能为 null :Context 的实现强制键和值非 null,因此 get 不会返回 null。
  • 性能考虑 :ContextView 的实现(如 ContextN)内部使用 HashMap,查询效率 O(1)。特化类(Context1~Context5)更轻量。
  • 与 ThreadLocal 的区别 :ContextView 绑定在订阅上,随数据流传递,而非线程。

七、代码片段示例

java 复制代码
// 创建上下文
Context ctx = Context.of("userId", 123, "traceId", "abc-123");

// 只读视图
ContextView view = ctx;

// 获取值
int userId = view.get("userId");                   // 123
String trace = view.getOrDefault("traceId", "default");
Optional<String> maybeTrace = view.getOrEmpty("traceId");

// 遍历
view.forEach((k, v) -> System.out.println(k + "=" + v));

// 转换为 Stream
view.stream().forEach(System.out::println);

通过上述分析,可以看出 ContextView 是 Reactor 响应式编程中传递元数据的标准化只读接口,其设计兼顾了安全、性能与易用性。理解它有助于更好地利用上下文传递功能,编写更健壮的响应式组件。

Reactor SynchronousSink 接口详解:同步生成器的核心契约

SynchronousSink<T> 是 Project Reactor 中用于同步、逐项生成数据 的核心接口。它通常与 Flux.generate 或 Mono.create 配合使用,允许开发者在每次被请求时同步地发出一个、多个或结束信号。理解该接口对于自定义响应式数据源至关重要。


一、接口概述

java 复制代码
public interface SynchronousSink<T> {
    void complete();
    @Deprecated Context currentContext();
    default ContextView contextView() { return currentContext(); }
    void error(Throwable e);
    void next(T t);
}
  • 角色 :作为生成器函数(Function / Consumer)与下游订阅者之间的桥梁,提供信号发送(next、complete、error)和上下文访问能力。
  • 同步限制 :所有方法必须在调用生成器的线程上同步执行 ,不能用于异步回调(此时应使用 FluxSink 或 MonoSink)。
  • 单次信号 :在一次 generate 调用中,next 最多调用一次,且随后可调用一次 complete 或 error,但不可同时调用多个终端信号。

二、方法逐行解析

1. void complete()

java 复制代码
/**
 * @see Subscriber#onComplete()
 */
void complete();

作用 :发出完成信号 ,表示数据流结束。下游订阅者将收到 onComplete() 回调。

约束:

  • 调用后该 SynchronousSink 实例不应再被使用。
  • 若同时调用 next 和 complete,行为取决于实现(通常允许先 next 后 complete,但不可颠倒)。
  • 与 error 互斥,只能调用其中一种终端信号。

典型用法 :在 generate 中,当状态不再产生新元素时调用 complete() 终止流。


2. @Deprecated Context currentContext()

java 复制代码
/**
 * @deprecated To be removed in 3.6.0 at the earliest. Prefer using #contextView() instead.
 */
@Deprecated
Context currentContext();

作用 :返回当前订阅者的完整 Context 对象(可读可写,虽然实际不应写入)。

弃用原因:

  • 返回 Context 接口(可写),但 SynchronousSink 只应读取上下文,不应修改。
  • 为了贯彻读写分离原则,Reactor 3.4.0 引入了 ContextView(只读),并在 3.5.0 后弃用本方法,推荐使用 contextView()。

替代方案 :使用新的默认方法 contextView()。


3. default ContextView contextView()

java 复制代码
/**
 * Return the current subscriber's context as a {@link ContextView} for inspection.
 */
default ContextView contextView() {
    return currentContext();
}

作用 :返回当前订阅者上下文的只读视图 (ContextView)。

设计意图:

  • 提供类型安全的只读访问,防止意外修改。
  • 是 currentContext() 的替代品,未来将取代后者。

实现 :默认委托给已弃用的 currentContext(),但具体实现类可直接覆写以返回 ContextView 实例。

使用场景 :在生成器函数中读取下游通过 contextWrite 注入的元数据(如 userId、traceId)。


4. void error(Throwable e)

java 复制代码
/**
 * @param e the exception to signal, not null
 * @see Subscriber#onError(Throwable)
 */
void error(Throwable e);

作用 :发出错误信号 ,终止流并将异常传递给下游 onError。

约束:

  • e 不能为 null。
  • 与 complete 互斥,只能调用其中一种。
  • 调用后不能再发送任何信号。

典型用法:在生成过程中发生不可恢复的错误时,终止流并传递异常。


5. void next(T t)

java 复制代码
/**
 * Try emitting, might throw an unchecked exception.
 *
 * @param t the value to emit, not null
 * @throws RuntimeException in case of unchecked error during the emission
 * @see Subscriber#onNext(Object)
 */
void next(T t);

作用 :发出一个数据元素给下游。

约束:

  • t 不能为 null(Reactive Streams 规范禁止 null 元素)。
  • 在一次 generate 调用中,next 最多调用一次 (这是与 FluxSink 的主要区别)。
  • 调用后可选地调用 complete 或 error 结束流。

异常 :若底层订阅者取消或发生错误,调用可能抛出 RuntimeException。


三、与 Context / ContextView 的关系

关系图解

复制代码
┌─────────────────┐          ┌─────────────────────┐
│   ContextView    │<────────│  SynchronousSink    │
│  (只读接口)      │          │  (上下文访问者)      │
└────────┬────────┘          └─────────────────────┘
         │
         │ extends
         ▼
┌─────────────────┐
│    Context      │
│  (可写接口)      │
└─────────────────┘
  • ContextView :定义只读操作(get、hasKey、stream 等),是 SynchronousSink 所依赖的上下文访问接口。
  • Context :继承 ContextView,并增加写入方法(put、delete 等),返回新实例。
  • SynchronousSink :通过 contextView() 获取当前订阅者的 ContextView,从而读取上下文信息;其 currentContext()(已弃用)返回 Context,但不应使用写入功能。

为什么不用 Context 而用 ContextView?

  • 安全:生成器函数应只读上下文,避免意外修改导致线程安全问题或副作用。
  • 清晰:明确表明该接口不提供写入能力,符合单一职责原则。

在 generate 中的典型用法:

java 复制代码
Flux.generate(
    () -> 0,
    (state, sink) -> {
        ContextView ctx = sink.contextView();
        String traceId = ctx.getOrDefault("traceId", "default");
        // 使用 traceId 记录日志或调整行为
        if (state < 10) {
            sink.next(state);
            return state + 1;
        } else {
            sink.complete();
            return state;
        }
    }
);

四、设计考量与注意事项

  1. 同步性 :SynchronousSink 的方法必须在生成器的线程上同步调用,不能存储引用后在异步回调中使用。
  2. 一次 next :与 FluxSink(允许多次 next)不同,SynchronousSink 每次生成器调用最多发出一个元素,适用于有状态、逐项生成的场景(如 generate)。
  3. 非空约束 :所有信号(next 的值、error 的异常)均不允许为 null。
  4. 上下文读取时机 :contextView() 在订阅时可用,反映下游订阅者通过 contextWrite 注入的最新上下文。

五、总结

  • SynchronousSink 是同步生成器的核心信号发送接口,提供 next、complete、error 三种信号,以及只读的上下文访问。
  • 与上下文的关系 :通过 contextView()(或已弃用的 currentContext())暴露当前订阅者的 ContextView,使生成器能感知下游元数据。
  • 设计进化 :currentContext() 弃用并替换为 contextView(),体现了 Reactor 对读写分离和类型安全的持续改进。

掌握 SynchronousSink 是熟练运用 Flux.generate 进行自定义同步数据生成的基础,也是理解 Reactor 数据流控制流程的重要一环。

Reactor Fuseable 接口详解:响应式流融合优化的核心 API

Fuseable 是 Project Reactor 中用于**流融合(Stream Fusion)**优化的关键接口。它允许数据流在操作符链中进行深度优化,减少内存分配和请求/响应开销,提升吞吐量。本文将逐段解析 Fuseable 接口的完整源码,说明其设计意图、各内部接口的作用,以及与先前讨论的 Context、SynchronousSink 等组件的关系。


一、接口概述与设计背景

java 复制代码
package reactor.core;

// ... imports

/**
 * A micro API for stream fusion, in particular marks producers that support a {@link QueueSubscription}.
 */
public interface Fuseable {
    // 常量定义
    int NONE = 0;
    int SYNC = 1;
    int ASYNC = 2;
    int ANY = 3;
    int THREAD_BARRIER = 0b100; //4

    // 静态辅助方法
    static String fusionModeName(int mode) { ... }
    static String fusionModeName(int mode, boolean ignoreThreadBarrier) { ... }

    // 内部接口
    interface ConditionalSubscriber<T> extends CoreSubscriber<T> { ... }
    interface QueueSubscription<T> extends Queue<T>, Subscription { ... }
    interface SynchronousSubscription<T> extends QueueSubscription<T> { ... }
    interface ScalarCallable<T> extends Callable<T> { }
}

设计目标:

  • 减少操作符之间的开销,例如将多个操作符合并为单个队列操作(poll),避免每次元素传递都涉及订阅请求。
  • 区分同步融合(SYNC)和异步融合(ASYNC),以及表示"不支持融合"的 NONE。
  • 提供标记接口,使发布者和订阅者能够协商是否以及如何融合。

二、常量定义

java 复制代码
int NONE = 0;           // 不支持任何融合模式
int SYNC = 1;           // 支持同步融合(拉取模式,无背压请求)
int ASYNC = 2;          // 支持异步融合(拉取模式,但仍需背压管理)
int ANY = 3;            // 请求者不指定模式,由生产者决定
int THREAD_BARRIER = 4; // 标记位,指示数据可能跨线程提取,需注意线程安全
  • SYNC 与 ASYNC :同步融合适用于所有元素已在内存中的源(如 Flux.just),可通过 poll() 直接拉取,无需 request 调用。异步融合适用于非阻塞异步源(如 Flux.range 虽同步但也可),仍需处理背压但可复用队列。
  • ANY :当操作符不关心具体模式时,请求 ANY,让生产者返回其最佳支持的模式。
  • THREAD_BARRIER :标记位,与模式组合(如 SYNC | THREAD_BARRIER),表示 poll() 可能被另一个线程调用(例如在 publishOn 后),消费者需注意线程安全性。

三、静态方法 fusionModeName

java 复制代码
static String fusionModeName(int mode) {
    return fusionModeName(mode, false);
}

static String fusionModeName(int mode, boolean ignoreThreadBarrier) {
    int evaluated = mode;
    String threadBarrierSuffix = "";
    if (mode >= 0) {
        evaluated = mode & ~THREAD_BARRIER; // 移除标志位
        if (!ignoreThreadBarrier && (mode & THREAD_BARRIER) == THREAD_BARRIER) {
            threadBarrierSuffix = "+THREAD_BARRIER";
        }
    }

    switch (evaluated) {
        case -1: return "Disabled"; // 特殊值,表示融合完全禁用
        case Fuseable.NONE: return "NONE" + threadBarrierSuffix;
        case Fuseable.SYNC: return "SYNC" + threadBarrierSuffix;
        case Fuseable.ASYNC: return "ASYNC" + threadBarrierSuffix;
        default: return "Unknown(" + evaluated + ")" + threadBarrierSuffix;
    }
}
  • 作用:将融合模式整数值转换为人类可读的字符串,便于调试和日志输出。
  • 逻辑 :
    1. 如果 mode >= 0,先清除 THREAD_BARRIER 位(通过 & ~THREAD_BARRIER),并检查是否设置了该标志,若设置且 ignoreThreadBarrier 为 false,则追加 "+THREAD_BARRIER" 后缀。
    2. 根据清除后的 evaluated 值匹配常量,返回对应名称;若为 -1(特殊禁用标志)返回 "Disabled";其他未知值返回 "Unknown(x)"。
  • 设计注意:该方法仅用于人类可读输出,不应用于逻辑判断,因为命名可能变化。

四、内部接口 ConditionalSubscriber

java 复制代码
interface ConditionalSubscriber<T> extends CoreSubscriber<T> {
    /**
     * Try consuming the value and return true if successful.
     * @param t the value to consume, not null
     * @return true if consumed, false if dropped and a new value can be immediately sent
     */
    boolean tryOnNext(T t);
}
  • 作用 :扩展 CoreSubscriber,增加 tryOnNext 方法,允许订阅者尝试消费一个元素,并返回是否成功。
  • 优化点 :当元素被过滤或丢弃时,返回 false,上游可以立即尝试发送下一个元素,而无需等待新的 request(1) 请求,减少往返开销。
  • 典型场景 :在 filter 操作符中,若谓词不匹配,订阅者可返回 false,上游直接发送下一个元素,无需额外请求。

五、核心接口 QueueSubscription<T>

java 复制代码
interface QueueSubscription<T> extends Queue<T>, Subscription {
    // 独有方法
    int requestFusion(int requestedMode);

    // 覆盖 Queue 的大部分方法,抛出 UnsupportedOperationException
    @Override default T peek() { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); }
    @Override default boolean add(@Nullable T t) { throw ... }
    @Override default boolean offer(@Nullable T t) { throw ... }
    @Override default T remove() { throw ... }
    @Override default T element() { throw ... }
    @Override default boolean contains(@Nullable Object o) { throw ... }
    @Override default Iterator<T> iterator() { throw ... }
    @Override default Object[] toArray() { throw ... }
    @Override default <T1> T1[] toArray(T1[] a) { throw ... }
    @Override default boolean remove(@Nullable Object o) { throw ... }
    @Override default boolean containsAll(Collection<?> c) { throw ... }
    @Override default boolean addAll(Collection<? extends T> c) { throw ... }
    @Override default boolean removeAll(Collection<?> c) { throw ... }
    @Override default boolean retainAll(Collection<?> c) { throw ... }
}
  • 作用 :同时作为 Queue(数据队列)和 Subscription(背压控制),允许下游通过 poll() 拉取数据,而无需每次 request 和 onNext 的配对。
  • 关键方法 requestFusion(int requestedMode) :
    • 由操作符调用,传入其期望的融合模式(SYNC、ASYNC 或 ANY)。
    • 生产者根据自身能力返回实际支持的模式(SYNC、ASYNC 或 NONE)。
    • 如果返回 SYNC,则下游可假定所有数据可通过 poll() 直接拉取完成,无需调用 request。
    • 如果返回 ASYNC,则仍需要调用 request 来触发数据生产,但数据通过 poll() 拉取,节省了 onNext 回调开销。
  • Queue 方法的默认实现 :抛出 UnsupportedOperationException,因为 QueueSubscription 仅要求实现 poll、clear、size、isEmpty 等基本队列方法,其他方法(如 add、remove)在流上下文中无意义,默认禁用。
  • NOT_SUPPORTED_MESSAGE :明确说明 QueueSubscription 是内部用途,不应当作普通 Queue 使用。

六、SynchronousSubscription 标记接口

java 复制代码
interface SynchronousSubscription<T> extends QueueSubscription<T> {
    @Override
    default int requestFusion(int requestedMode) {
        if ((requestedMode & Fuseable.SYNC) != 0) {
            return Fuseable.SYNC;
        }
        return NONE;
    }
}
  • 作用 :表示该订阅器支持同步融合(所有数据立即可用)。
  • requestFusion 默认实现 :如果请求的模式包含 SYNC(即 requestedMode & SYNC != 0),则返回 SYNC;否则返回 NONE。
  • 简化实现 :实现此接口的类只需确保 poll() 可以阻塞或不阻塞地返回数据,不需要额外编写 requestFusion 逻辑。

七、ScalarCallable<T> 标记接口

java 复制代码
interface ScalarCallable<T> extends Callable<T> { }
  • 作用 :标记一个发布者能够同步返回一个标量值(或 null),常用于 Mono.just 等场景。
  • 优化 :在编译时或运行时,框架可以直接调用 call() 获取值,避免构建完整的响应式链。
  • 与 Fuseable 关系:独立标记接口,常与融合机制结合,但本身不提供融合逻辑。

八、与之前讲解组件的关系

组件 关系
Context / ContextView 正交关系。Fuseable 关注性能优化,Context 关注元数据传递。某些融合操作符可能需要读取 Context,但 Fuseable 本身不依赖它们。
SynchronousSink 使用场景不同。SynchronousSink 用于 Flux.generate 的同步生成,而 Fuseable 用于操作符之间的优化。两者可能在 generate 融合时协同工作,但无直接依赖。
CoreSubscriber ConditionalSubscriber 扩展了 CoreSubscriber,因此融合机制建立在订阅者扩展上。
Subscription 与 Queue QueueSubscription 同时实现两者,将数据拉取与背压控制统一。

九、融合协商流程示例

  1. 下游操作符(如 map)通过 requestFusion(ANY) 向上游请求融合。
  2. 上游(如 Flux.just)返回 SYNC,表示支持同步融合。
  3. 下游将上游的 QueueSubscription 视为队列,通过 poll() 逐个获取元素,无需调用 request 和 onNext。
  4. 这样整个链中的多个操作符可能合并为一次 poll 循环,大幅减少方法调用开销。

十、总结

  • Fuseable 是 Reactor 内部性能优化的核心 API,通过融合减少请求-回调开销。
  • 关键内部接口 :
    • ConditionalSubscriber:允许尝试消费并返回结果,优化过滤场景。
    • QueueSubscription:将队列与订阅结合,支持拉取模式。
    • SynchronousSubscription:简化同步源的融合协商。
    • ScalarCallable:标记标量源,便于快速获取值。
  • 与先前组件关系 :Fuseable 主要作用于发布者和订阅者的实现层,与上下文传递(ContextView)和同步生成器(SynchronousSink)领域正交,但共同构成 Reactor 的完整功能体系。

掌握 Fuseable 有助于理解 Reactor 内部的高性能原理,并能在自定义操作符中充分利用融合优化。

相关推荐
企业数字化笔记2 分钟前
视频成片怎么稳定导出?编码选择、GPU/CPU降级与媒体交付验收
java·python·ffmpeg
萧瑟余晖30 分钟前
Spring IoC容器与Bean生命周期详解
spring
Wx-bishekaifayuan43 分钟前
springboot房屋租赁系统11574-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·sql·spring·课程设计
三8441 小时前
Fastjson 漏洞学习笔记 · 03 · 经典利用链:TemplatesImpl 与 JdbcRowSetImpl
java·web安全·fastjson
蜗牛互联网1 小时前
GPT-6.1 Sol迁移指南:从token单价转向每任务成本门禁
java·人工智能·后端·gpt
蜗牛互联网2 小时前
HSTU在Dynamo-Triton中的AOTI与KV缓存验收方法
java·人工智能·后端·缓存
Sirens.2 小时前
Java多线程实例:单例模式、阻塞队列、线程池与定时器
java·开发语言·单例模式
青山木2 小时前
秒杀系统设计(一):需求拆解与流量治理
java·数据库·redis·后端·架构
卓怡学长2 小时前
w195基于ssm“Fitfrend”个性化健身
java·intellij-idea
驭渊的小故事2 小时前
算法实战:滑动窗口、素数枚举与字符串模拟(三道经典题解)
java·算法