Spring WebFlux核心原理-对比Servlet与响应式模型

目录

[🎯 我踩过的坑](#🎯 我踩过的坑)

[✨ 摘要](#✨ 摘要)

[1. 别急着用WebFlux,先搞清楚这两个问题](#1. 别急着用WebFlux,先搞清楚这两个问题)

[1.1 什么时候该用WebFlux?](#1.1 什么时候该用WebFlux?)

[1.2 性能神话 vs 现实](#1.2 性能神话 vs 现实)

[2. 线程模型:一个线程 vs 少量线程](#2. 线程模型:一个线程 vs 少量线程)

[2.1 Servlet的"一个请求一个线程"](#2.1 Servlet的"一个请求一个线程")

[2.2 WebFlux的"事件循环"](#2.2 WebFlux的"事件循环")

[3. 请求处理流程对比](#3. 请求处理流程对比)

[3.1 Spring MVC的请求处理链](#3.1 Spring MVC的请求处理链)

[3.2 WebFlux的请求处理链](#3.2 WebFlux的请求处理链)

[4. 内存管理:堆内 vs 堆外](#4. 内存管理:堆内 vs 堆外)

[4.1 Servlet的内存模型](#4.1 Servlet的内存模型)

[4.2 WebFlux的零拷贝与堆外内存](#4.2 WebFlux的零拷贝与堆外内存)

[5. 连接处理:短连接 vs 长连接](#5. 连接处理:短连接 vs 长连接)

[5.1 Servlet的短连接瓶颈](#5.1 Servlet的短连接瓶颈)

[5.2 WebFlux的长连接优势](#5.2 WebFlux的长连接优势)

[6. 错误处理:同步 vs 异步](#6. 错误处理:同步 vs 异步)

[6.1 Servlet的错误处理](#6.1 Servlet的错误处理)

[6.2 WebFlux的错误处理](#6.2 WebFlux的错误处理)

[7. 实战:构建API网关](#7. 实战:构建API网关)

[7.1 需求分析](#7.1 需求分析)

[7.2 Spring Cloud Gateway实现](#7.2 Spring Cloud Gateway实现)

[7.3 性能压测结果](#7.3 性能压测结果)

[8. 常见问题与解决方案](#8. 常见问题与解决方案)

[8.1 阻塞代码问题](#8.1 阻塞代码问题)

[8.2 内存泄漏排查](#8.2 内存泄漏排查)

[8.3 调试技巧](#8.3 调试技巧)

[9. 性能优化指南](#9. 性能优化指南)

[9.1 线程池优化](#9.1 线程池优化)

[9.2 内存优化](#9.2 内存优化)

[9.3 背压处理](#9.3 背压处理)

[10. 迁移指南:从Servlet到WebFlux](#10. 迁移指南:从Servlet到WebFlux)

[10.1 渐进式迁移策略](#10.1 渐进式迁移策略)

[10.2 兼容性处理](#10.2 兼容性处理)

[11. 最后的话](#11. 最后的话)

[📚 推荐阅读](#📚 推荐阅读)

官方文档

源码学习

最佳实践

性能工具


🎯 我踩过的坑

去年我们团队要做一个实时消息推送系统,要求支持10万并发长连接。开始用的Spring MVC + Tomcat,测试时1000连接就崩了,Tomcat线程池直接打满。

后来换成WebFlux + Netty,同样的机器配置,10万连接稳稳的。但高兴得太早,上线后发现CPU使用率比原来高了30%。排查发现是有个同事在响应式链里写了阻塞代码。

还有个更坑的:我们有个批量查询接口,用WebFlux重写后,响应时间反而变长了。查了三天,发现是因为没处理好背压,数据量大的时候内存暴涨。

这些经历让我明白:不懂原理的技术选型就是耍流氓,WebFlux不是银弹,用错了比不用还惨

✨ 摘要

Spring WebFlux是响应式Web框架,基于Reactor和Netty实现。本文深度对比Servlet阻塞模型与响应式非阻塞模型的差异,从线程模型、IO处理、内存管理三个维度解析性能关键。通过源码分析揭示WebFlux的请求处理流程、编解码机制、错误处理策略。结合压测数据和实战案例,提供WebFlux的正确使用姿势和性能优化指南。

1. 别急着用WebFlux,先搞清楚这两个问题

1.1 什么时候该用WebFlux?

很多人盲目追新技术,我总结了几个判断标准:

适合WebFlux的场景

  • ✅ 高并发IO密集型应用(API网关、消息推送)

  • ✅ 实时数据流处理(股票行情、实时监控)

  • ✅ 需要大量长连接的场景(WebSocket、SSE)

  • ✅ 微服务间的非阻塞调用

不适合WebFlux的场景

  • ❌ CPU密集型计算(大数据分析、复杂算法)

  • ❌ 已有大量阻塞代码的遗留系统

  • ❌ 团队对响应式编程不熟悉

  • ❌ 简单CRUD应用,并发不高

1.2 性能神话 vs 现实

先看个真实的性能对比,这是我们在4核8G机器上的测试结果:

场景 Spring MVC (Tomcat) WebFlux (Netty) 提升
100并发,简单查询 3200 QPS 3500 QPS 9.4%
1000并发,简单查询 2800 QPS 5200 QPS 85.7%
100并发,IO密集 1200 QPS 3800 QPS 216.7%
内存占用(1000连接) 450MB 320MB 28.9%

看到没?只有在高并发和IO密集型场景下,WebFlux才有明显优势

2. 线程模型:一个线程 vs 少量线程

2.1 Servlet的"一个请求一个线程"

这是Servlet最经典的模型,也是最大的瓶颈:

java 复制代码
// Tomcat的线程池处理请求
public class Http11Processor implements Runnable {
    
    public void run() {
        try {
            // 1. 解析请求
            parseRequest();
            
            // 2. 调用Servlet(线程在这里阻塞!)
            servlet.service(request, response);
            
            // 3. 返回响应
            writeResponse();
        } catch (Exception e) {
            handleError(e);
        }
    }
}

// Spring MVC的Controller
@RestController
public class UserController {
    
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        // 线程在这里阻塞,等待数据库查询
        return userRepository.findById(id).orElse(null);
    }
}

代码清单1:Servlet线程模型

用图表示更清楚:

图1:Servlet阻塞线程模型

问题:线程在等待IO时被阻塞,什么也干不了,浪费资源。

2.2 WebFlux的"事件循环"

WebFlux用了完全不同的思路:

java 复制代码
// Netty的事件循环
public class NioEventLoop extends SingleThreadEventLoop {
    
    protected void run() {
        for (;;) {
            try {
                // 1. 选择就绪的IO事件
                int readyChannels = selector.select();
                
                if (readyChannels > 0) {
                    // 2. 处理IO事件
                    processSelectedKeys();
                }
                
                // 3. 处理任务队列
                runAllTasks();
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }
}

// WebFlux的Controller
@RestController
public class UserController {
    
    @GetMapping("/user/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        // 立即返回Publisher,不阻塞
        return userRepository.findById(id)
            .subscribeOn(Schedulers.boundedElastic());
    }
}

代码清单2:WebFlux事件循环模型

响应式模型的工作方式:

图2:WebFlux事件驱动模型

关键点:事件循环线程只做IO调度,耗时操作交给工作线程池,处理完回调。

3. 请求处理流程对比

3.1 Spring MVC的请求处理链

看Spring MVC是怎么处理请求的:

java 复制代码
// DispatcherServlet的核心流程
public class DispatcherServlet extends FrameworkServlet {
    
    protected void doDispatch(HttpServletRequest request, 
                             HttpServletResponse response) throws Exception {
        
        // 1. 获取Handler
        HandlerExecutionChain mappedHandler = getHandler(request);
        
        // 2. 获取HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        
        // 3. 执行拦截器前置处理
        if (!mappedHandler.applyPreHandle(request, response)) {
            return;
        }
        
        // 4. 实际处理(这里线程被阻塞!)
        ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
        
        // 5. 渲染视图
        mappedHandler.applyPostHandle(request, response, mv);
        processDispatchResult(request, response, mappedHandler, mv, null);
    }
}

代码清单3:Spring MVC请求处理

处理流程的时间线:

图3:Spring MVC线程阻塞时间线

看到没?线程大部分时间在等待,啥也干不了

3.2 WebFlux的请求处理链

WebFlux的处理方式完全不同:

java 复制代码
// DispatcherHandler的核心流程
public class DispatcherHandler implements WebHandler {
    
    public Mono<Void> handle(ServerWebExchange exchange) {
        return Flux.fromIterable(this.handlerMappings)
            .concatMap(mapping -> mapping.getHandler(exchange))
            .next()
            .switchIfEmpty(createNotFoundError())
            .flatMap(handler -> invokeHandler(exchange, handler))
            .flatMap(result -> handleResult(exchange, result));
    }
    
    private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, 
                                             Object handler) {
        // 找到合适的HandlerAdapter
        HandlerAdapter adapter = getHandlerAdapter(handler);
        
        // 执行业务逻辑(返回Mono,不阻塞)
        return adapter.handle(exchange, handler);
    }
}

代码清单4:WebFlux请求处理

用序列图表示更清楚:

图4:WebFlux非阻塞处理序列图

关键优势:事件循环线程永不阻塞,一个线程能处理成千上万的连接。

4. 内存管理:堆内 vs 堆外

4.1 Servlet的内存模型

Servlet使用的是传统的堆内存:

java 复制代码
// 典型的Servlet内存使用
public class UserController {
    
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) {
        // 整个文件被读到堆内存!
        byte[] bytes = file.getBytes();  // 内存峰值
        
        // 处理文件
        processFile(bytes);
        
        return "success";
    }
}

内存分配情况

  • 每个请求独立的内存空间

  • 大文件上传时内存暴涨

  • GC压力大,容易Full GC

4.2 WebFlux的零拷贝与堆外内存

WebFlux利用Netty的零拷贝和堆外内存:

java 复制代码
@RestController
public class FileController {
    
    @PostMapping(value = "/upload", 
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<String> upload(FilePart file) {
        // 使用数据流,不一次性加载到内存
        return file.content()
            .map(dataBuffer -> {
                // 直接操作数据缓冲区
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);
                return bytes;
            })
            .collectList()
            .flatMap(chunks -> {
                // 合并处理
                return processChunks(chunks);
            });
    }
    
    // 文件下载:零拷贝
    @GetMapping(value = "/download/{id}", 
               produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public Mono<Resource> download(@PathVariable String id) {
        return Mono.fromCallable(() -> 
            new FileSystemResource("/data/files/" + id));
    }
}

代码清单5:WebFlux文件处理

内存对比图:

图5:内存模型对比

性能测试(上传100MB文件,100并发):

指标 Spring MVC WebFlux 对比
内存峰值 2.5GB 800MB 减少68%
GC时间 4.2s 0.8s 减少81%
平均耗时 3.4s 1.8s 减少47%

5. 连接处理:短连接 vs 长连接

5.1 Servlet的短连接瓶颈

Tomcat默认使用短连接,每个请求创建新连接:

java 复制代码
// Tomcat连接处理
public class Http11Processor {
    
    public void action(ActionCode actionCode, Object param) {
        switch (actionCode) {
            case CLOSE:
                // 关闭连接
                endRequest();
                break;
            // ...
        }
    }
    
    private void endRequest() {
        // 释放连接资源
        inputBuffer.recycle();
        outputBuffer.recycle();
        
        // 如果是短连接,关闭Socket
        if (!isKeepAlive()) {
            closeSocket();
        }
    }
}

问题

  • 高并发时大量连接创建/销毁开销

  • 端口资源有限

  • 握手开销大(SSL握手更甚)

5.2 WebFlux的长连接优势

Netty天生支持长连接:

java 复制代码
// WebFlux的HTTP/2支持
@Configuration
public class Http2Config {
    
    @Bean
    public NettyServerCustomizer nettyServerCustomizer() {
        return httpServer -> httpServer
            .protocol(HttpProtocol.H2, HttpProtocol.HTTP11)
            .compress(true)
            .http2Settings(settings -> 
                settings.maxConcurrentStreams(1000L));
    }
}

// WebSocket支持
@RestController
public class ChatController {
    
    @MessageMapping("chat")
    public Flux<Message> chat(Flux<Message> inbound) {
        return inbound
            .doOnNext(message -> 
                log.info("收到消息: {}", message))
            .doOnComplete(() -> 
                log.info("连接关闭"));
    }
}

连接数对比测试(10万并发连接):

图6:连接处理能力对比

现实案例:我们做的消息推送系统,用Tomcat最多支撑5000长连接,用Netty轻松10万连接。

6. 错误处理:同步 vs 异步

6.1 Servlet的错误处理

Servlet的错误处理是同步的:

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        // 同步处理错误
        return ResponseEntity.status(500)
            .body("错误: " + e.getMessage());
    }
}

// 问题:如果异常处理器本身抛异常?
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<String> handleNPE(NullPointerException e) {
    String msg = e.getMessage();  // 可能NPE!
    return ResponseEntity.status(500).body(msg);
}

6.2 WebFlux的错误处理

WebFlux的错误处理是响应式的:

java 复制代码
@ControllerAdvice
public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {
    
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        return Mono.fromRunnable(() -> {
            // 可以在这里记录日志
            log.error("处理请求异常", ex);
        })
        .then(Mono.defer(() -> {
            // 构建错误响应
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            
            byte[] bytes = ("错误: " + ex.getMessage()).getBytes(UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            
            return response.writeWith(Mono.just(buffer));
        }))
        .onErrorResume(e -> {
            // 错误处理器本身出错时的fallback
            log.error("错误处理器异常", e);
            return Mono.empty();
        });
    }
}

代码清单6:WebFlux错误处理

错误处理链对比:

图7:错误处理机制对比

7. 实战:构建API网关

7.1 需求分析

我们需要一个高性能API网关,要求:

  • 支持5万QPS

  • 动态路由

  • 熔断降级

  • 限流鉴权

  • 实时监控

7.2 Spring Cloud Gateway实现

java 复制代码
@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user_service", r -> r
                .path("/api/users/**")
                .filters(f -> f
                    .addRequestHeader("X-Request-ID", UUID.randomUUID().toString())
                    .circuitBreaker(config -> config
                        .setName("userService")
                        .setFallbackUri("forward:/fallback/user"))
                    .requestRateLimiter(config -> config
                        .setRateLimiter(redisRateLimiter()))
                    .retry(config -> config
                        .setRetries(3)
                        .setStatuses(HttpStatus.SERVICE_UNAVAILABLE)))
                .uri("lb://user-service"))
            .build();
    }
    
    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(100, 200, 1);
    }
}

// 全局过滤器
@Component
public class AuthFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, 
                            GatewayFilterChain chain) {
        String token = exchange.getRequest()
            .getHeaders().getFirst("Authorization");
        
        if (token == null) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        return validateToken(token)
            .flatMap(valid -> {
                if (valid) {
                    return chain.filter(exchange);
                } else {
                    exchange.getResponse()
                        .setStatusCode(HttpStatus.UNAUTHORIZED);
                    return exchange.getResponse().setComplete();
                }
            });
    }
    
    private Mono<Boolean> validateToken(String token) {
        return webClient.post()
            .uri("http://auth-service/validate")
            .bodyValue(new TokenRequest(token))
            .retrieve()
            .bodyToMono(TokenResponse.class)
            .map(TokenResponse::isValid)
            .timeout(Duration.ofSeconds(3))
            .onErrorReturn(false);
    }
}

代码清单7:Spring Cloud Gateway配置

7.3 性能压测结果

测试环境

  • 4核8GB * 3节点

  • 后端服务:Spring Boot 2.7

  • 压测工具:wrk

测试结果

场景 Spring Cloud Zuul Spring Cloud Gateway 提升
100并发,简单路由 4200 QPS 18500 QPS 340%
1000并发,复杂过滤 1200 QPS 8500 QPS 608%
长连接支持 优秀 -
内存占用 650MB 320MB 51%

8. 常见问题与解决方案

8.1 阻塞代码问题

这是WebFlux最常见的问题:

java 复制代码
// 错误:在响应式链中阻塞
@GetMapping("/blocking")
public Mono<String> blocking() {
    return Mono.fromCallable(() -> {
        Thread.sleep(1000);  // 阻塞!
        return "done";
    });
}

// 正确:使用调度器
@GetMapping("/non-blocking")
public Mono<String> nonBlocking() {
    return Mono.fromCallable(() -> {
        Thread.sleep(1000);
        return "done";
    })
    .subscribeOn(Schedulers.boundedElastic());
}

// 使用BlockHound检测阻塞调用
@PostConstruct
public void init() {
    BlockHound.install();
}

8.2 内存泄漏排查

响应式编程容易内存泄漏:

java 复制代码
// 监控内存使用
@RestController
public class MetricsController {
    
    @GetMapping("/metrics/memory")
    public Mono<Map<String, Object>> memoryMetrics() {
        return Mono.fromCallable(() -> {
            Runtime runtime = Runtime.getRuntime();
            Map<String, Object> metrics = new HashMap<>();
            
            metrics.put("totalMemory", runtime.totalMemory());
            metrics.put("freeMemory", runtime.freeMemory());
            metrics.put("maxMemory", runtime.maxMemory());
            metrics.put("directMemory", PlatformDependent.usedDirectMemory());
            
            return metrics;
        });
    }
}

// 使用JVM参数开启详细GC日志
// -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

8.3 调试技巧

java 复制代码
// 1. 开启操作符堆栈跟踪
Hooks.onOperatorDebug();

// 2. 添加检查点
flux.checkpoint("source")
   .map(...)
   .checkpoint("afterMap")
   .subscribe();

// 3. 使用Micrometer监控
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
    return registry -> registry.config()
        .commonTags("application", "webflux-demo");
}

// 4. 记录慢请求
@Bean
public WebFilter slowRequestFilter() {
    return (exchange, chain) -> {
        long start = System.currentTimeMillis();
        
        return chain.filter(exchange)
            .doFinally(signal -> {
                long cost = System.currentTimeMillis() - start;
                if (cost > 1000) {
                    log.warn("慢请求: {} {}, 耗时: {}ms",
                        exchange.getRequest().getMethod(),
                        exchange.getRequest().getPath(),
                        cost);
                }
            });
    };
}

9. 性能优化指南

9.1 线程池优化

复制代码
# application.yml
spring:
  webflux:
    client:
      max-connections: 1000
      max-memory-size: 10MB
      
server:
  netty:
    connection-timeout: 30s
    idle-timeout: 60s
    
reactor:
  schedulers:
    default-pool-size: 4
    bounded-elastic:
      max-threads: 200
      queue-size: 10000
      ttl: 60

9.2 内存优化

java 复制代码
// 使用直接内存池
@Bean
public NettyServerCustomizer nettyServerCustomizer() {
    return server -> server
        .metrics(true, () -> 
            new MicrometerChannelMetricsRecorder("server", "http"))
        .accessLog(true)
        .runOn(LoopResources.create("webflux", 4, true))
        .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
        .childOption(ChannelOption.ALLOCATOR, 
            PooledByteBufAllocator.DEFAULT);
}

// 监控直接内存
@Scheduled(fixedRate = 5000)
public void monitorDirectMemory() {
    long used = PlatformDependent.usedDirectMemory();
    long max = PlatformDependent.maxDirectMemory();
    
    if (used > max * 0.8) {
        log.warn("直接内存使用过高: {}/{}", used, max);
    }
}

9.3 背压处理

java 复制代码
@GetMapping("/stream")
public Flux<Data> streamData() {
    return dataService.getDataStream()
        .onBackpressureBuffer(1000, 
            BufferOverflowStrategy.DROP_OLDEST)
        .doOnNext(data -> 
            metrics.recordDataProcessed())
        .delayElements(Duration.ofMillis(10))  // 控制速率
        .timeout(Duration.ofSeconds(30))
        .doOnError(TimeoutException.class, e -> 
            log.error("数据流超时"));
}

10. 迁移指南:从Servlet到WebFlux

10.1 渐进式迁移策略

不要一次性重写整个系统:

图8:渐进式迁移策略

10.2 兼容性处理

java 复制代码
// 1. 混合使用
@Configuration
public class HybridConfig {
    
    // WebFlux路由
    @Bean
    public RouterFunction<ServerResponse> webFluxRoutes() {
        return RouterFunctions.route()
            .GET("/reactive/users", this::getUsers)
            .build();
    }
    
    // Spring MVC Controller
    @RestController
    @RequestMapping("/mvc")
    public class MvcController {
        @GetMapping("/users")
        public List<User> getUsers() {
            return userService.getUsers();
        }
    }
}

// 2. 响应式调用阻塞服务
@Service
public class HybridService {
    
    public Mono<List<User>> getUsersReactive() {
        return Mono.fromCallable(() -> 
            // 调用现有的阻塞服务
            blockingUserService.getUsers()
        )
        .subscribeOn(Schedulers.boundedElastic())
        .flatMapMany(Flux::fromIterable)
        .collectList();
    }
}

11. 最后的话

WebFlux不是Spring MVC的替代品,它们是解决不同问题的工具。就像螺丝刀和锤子,用对了事半功倍,用错了事倍功半。

我见过太多团队在技术选型上犯错:有的在简单CRUD系统上用WebFlux,复杂度增加但收益不大;有的在高并发场景下死守Servlet,性能上不去还怪硬件不行。

记住:技术是为业务服务的,选型要结合业务场景、团队能力、运维成本综合考虑。

📚 推荐阅读

官方文档

  1. **Spring WebFlux官方文档**​ - 最权威的参考

  2. **Project Reactor文档**​ - 响应式编程基础

源码学习

  1. **Spring WebFlux源码**​ - 直接看源码

  2. **Netty源码**​ - 网络层实现

最佳实践

  1. **Spring Cloud Gateway指南**​ - API网关实战

  2. **响应式系统设计**​ - 响应式宣言

性能工具

  1. **Micrometer监控**​ - 响应式应用监控

  2. **BlockHound**​ - 阻塞调用检测


最后建议 :不要因为WebFlux"时髦"就用它。先分析你的业务场景,做个小规模POC测试,用数据说话。记住:先验证,后落地;先小范围,后推广

相关推荐
難釋懷3 分钟前
解决状态登录刷新问题
java·开发语言·javascript
潇凝子潇5 分钟前
Java 设计支持动态调整的LFU缓存: 需包含热度衰减曲线和淘汰策略监控
java·spring·缓存
94甘蓝19 分钟前
第 5 篇 Spring AI - Tool Calling 全面解析:从基础到高级应用
java·人工智能·函数调用·工具调用·spring ai·tool calling
酉鬼女又兒33 分钟前
SQL113+114 更新记录(一)(二)+更新数据知识总结
java·服务器·前端
毅炼1 小时前
Netty 常见问题总结
java·网络·数据结构·算法·哈希算法
Anastasiozzzz1 小时前
leetcodehot100--最小栈 MinStack
java·javascript·算法
Sylvia-girl1 小时前
线程的死锁【了解】
java·开发语言·jvm
Elias不吃糖1 小时前
java开发的三层架构
java·开发语言·架构
pp起床1 小时前
【苍穹外卖】Day2.5 分类管理
java
lixin5565561 小时前
基于神经网络的音乐生成增强器
java·人工智能·pytorch·python·深度学习·语言模型