Java高质量编程【一、概述】

在Java生态中,构建高性能(High Performance)高可用(High Availability)高扩展性(High Scalability/Extensibility)的系统是架构师和高级开发者的核心目标。这三者往往相互制约(例如过度追求性能可能牺牲可维护性),因此需要权衡与最佳实践。

以下是针对这三个维度的详细原则、方法及正反代码示例。


一、高性能 (High Performance)

核心目标:降低延迟(Latency),提高吞吐量(Throughput),减少资源(CPU/内存)消耗。

1. 关键原则与方法

  • 减少对象创建与GC压力:避免在循环中创建临时对象,使用对象池,优先使用基本类型。
  • 并发编程优化
    • 优先使用无锁数据结构(如 LongAdder vs AtomicLong)。
    • 合理使用线程池,避免频繁创建销毁线程。
    • 利用 CompletableFuture 或虚拟线程(Java 21+)进行异步编排。
  • I/O 模型:从阻塞 I/O (BIO) 转向非阻塞 I/O (NIO),使用 Netty 或 Project Loom。
  • 缓存策略:多级缓存(本地 Caffeine + 分布式 Redis),注意缓存穿透/击穿/雪崩。
  • JVM 调优:选择合适的 GC 收集器(G1, ZGC),调整堆大小。

2. 正反示例:高并发计数器

❌ 反面示例:低性能(锁竞争严重)
使用 synchronizedAtomicLong 在极高并发下会导致严重的 CAS 自旋失败或线程阻塞。

public class LowPerformanceCounter {
    // 在高并发下,AtomicLong 的 CAS 操作会导致大量 CPU 空转(自旋)
    private final AtomicLong count = new AtomicLong(0);

    public void increment() {
        // 每次调用都涉及一次 CAS 操作,竞争激烈时性能急剧下降
        count.incrementAndGet();
    }

    public long getCount() {
        return count.get();
    }
}

✅ 正面示例:高性能(分段思想/LongAdder)
使用 LongAdder,它内部采用分段累加(类似 ConcurrentHashMap 的分段锁思想),仅在取值时合并,极大降低了竞争。

import java.util.concurrent.atomic.LongAdder;

public class HighPerformanceCounter {
    // LongAdder 在多线程高并发场景下性能远优于 AtomicLong
    private final LongAdder count = new LongAdder();

    public void increment() {
        // 不同线程更新不同的 Cell,减少冲突
        count.increment();
    }

    public long getCount() {
        // 仅在读取时进行汇总,读取频率通常远低于写入
        return count.sum();
    }
}

3. 正反示例:字符串拼接

❌ 反面示例
在循环中使用 + 拼接字符串,导致大量临时 StringBuilderString 对象创建,触发频繁 GC。

public String buildReportBad(List<String> items) {
    String result = "";
    for (String item : items) {
        // 每次循环都创建新的 StringBuilder 和 String 对象
        result += item + ","; 
    }
    return result;
}

✅ 正面示例
预分配容量的 StringBuilder

public String buildReportGood(List<String> items) {
    // 预估大小,减少扩容拷贝
    StringBuilder sb = new StringBuilder(items.size() * 10);
    for (String item : items) {
        sb.append(item).append(",");
    }
    return sb.toString();
}

二、高可用 (High Availability)

核心目标:系统在面对故障(硬件、网络、代码Bug、流量洪峰)时仍能持续提供服务,最小化停机时间(MTTR)。

1. 关键原则与方法

  • 故障隔离(Bulkheading):一个模块的故障不应拖垮整个系统(如线程池隔离、信号量隔离)。
  • 熔断与降级(Circuit Breaker & Fallback):当下游服务不可用时,快速失败并返回默认值,防止级联故障。
  • 超时控制(Timeouts):所有远程调用必须设置超时,避免线程被无限期占用。
  • 重试机制(Retry with Backoff):对瞬时故障进行指数退避重试,但要避免重试风暴。
  • 幂等性(Idempotency):确保重复请求不会产生副作用。

2. 正反示例:远程服务调用

❌ 反面示例:无保护调用(雪崩风险)
没有超时、没有熔断,一旦下游服务卡顿,当前服务线程池迅速耗尽,导致整个应用不可用。

public class VulnerableService {
    
    public UserData getUserData(String userId) {
        // 1. 没有超时设置:如果 rpcClient 卡住,线程将永久阻塞
        // 2. 没有熔断:下游挂了,这里会一直尝试,拖垮线程池
        // 3. 没有降级:直接抛出异常,用户看到 500 错误
        return rpcClient.callRemote(userId); 
    }
}

✅ 正面示例:使用 Resilience4j 进行防护
引入熔断器、超时控制和降级逻辑。

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.timelimiter.TimeLimiter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

public class ResilientService {
    
    private final CircuitBreaker circuitBreaker;
    private final TimeLimiter timeLimiter;
    private final ExecutorService executorService;

    public UserData getUserDataSafe(String userId) {
        try {
            // 组合使用:超时控制 + 熔断保护
            CompletableFuture<UserData> future = timeLimiter.executeFutureSupplier(
                () -> CompletableFuture.supplyAsync(() -> 
                    circuitBreaker.executeSupplier(() -> rpcClient.callRemote(userId))
                , executorService),
                java.time.Duration.ofSeconds(2) // 2秒超时
            );
            return future.join();
        } catch (Exception e) {
            // 降级逻辑:返回缓存数据或默认值,保证系统不崩
            return getFallbackUserData(userId); 
        }
    }

    private UserData getFallbackUserData(String userId) {
        // 从本地缓存获取或返回空对象
        return new UserData(userId, "DefaultName", true); 
    }
}

三、高扩展性 (High Scalability & Extensibility)

这里包含两个层面:

  1. Scalability (可伸缩性):通过增加机器轻松应对流量增长(水平扩展)。
  2. Extensibility (可扩展性/易维护性):代码结构易于添加新功能,符合开闭原则(OCP)。

1. 关键原则与方法

  • 无状态设计 (Stateless):服务节点不保存会话状态,便于负载均衡和横向扩容。
  • 领域驱动设计 (DDD) 与 分层架构:清晰的边界,业务逻辑与技术细节分离。
  • 开闭原则 (OCP):对扩展开放,对修改关闭。使用策略模式、模板方法模式、SPI 机制。
  • 事件驱动 (Event-Driven):通过消息队列解耦模块,实现异步扩展。
  • 依赖注入 (DI):利用 Spring 等框架管理生命周期和解耦。

2. 正反示例:支付渠道扩展

❌ 反面示例:硬编码/违反开闭原则
每新增一种支付方式(如 PayPal),都需要修改核心代码,增加 if-else,容易引入 Bug,且难以测试。

public class PaymentProcessor {

    public void pay(String type, double amount) {
        if ("ALIPAY".equals(type)) {
            // 支付宝逻辑
            connectToAlipay(amount);
        } else if ("WECHAT".equals(type)) {
            // 微信逻辑
            connectToWechat(amount);
        } else if ("CREDIT_CARD".equals(type)) {
            // 信用卡逻辑
            connectToBank(amount);
        } else {
            throw new IllegalArgumentException("Unsupported type");
        }
        // 记录日志、发送通知等逻辑混杂在一起
    }
    
    // 随着支付方式增加,这个类会变得极其庞大且难以维护
}

✅ 正面示例:策略模式 + 工厂/Spring 注入
定义统一接口,每种支付方式实现该接口。新增支付方式只需新增一个类,无需修改原有代码。

// 1. 定义策略接口
public interface PaymentStrategy {
    void pay(double amount);
    boolean supports(String type);
}

// 2. 具体实现
@Component
public class AlipayStrategy implements PaymentStrategy {
    public boolean supports(String type) { return "ALIPAY".equals(type); }
    public void pay(double amount) { /* 支付宝具体逻辑 */ }
}

@Component
public class WechatStrategy implements PaymentStrategy {
    public boolean supports(String type) { return "WECHAT".equals(type); }
    public void pay(double amount) { /* 微信具体逻辑 */ }
}

// 3. 上下文/工厂类 (高扩展性核心)
@Service
public class PaymentService {
    
    // 利用 Spring 自动注入所有实现了 PaymentStrategy 的 Bean
    private final List<PaymentStrategy> strategies;

    public PaymentService(List<PaymentStrategy> strategies) {
        this.strategies = strategies;
    }

    public void processPayment(String type, double amount) {
        // 查找对应的策略
        PaymentStrategy strategy = strategies.stream()
            .filter(s -> s.supports(type))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Unknown payment type: " + type));
        
        // 执行支付
        strategy.pay(amount);
        
        // 横切关注点(日志、监控)可以在此统一处理或通过 AOP 处理
    }
}

优势:如果要加 "Bitcoin" 支付,只需新建 BitcoinStrategy 类并加上 @Component,主流程代码一行都不用改。

3. 正反示例:水平扩展性 (Stateless)

❌ 反面示例:有状态服务
将用户 Session 存储在本地内存中,导致无法通过 Nginx 进行简单的轮询负载均衡(需要配置 Sticky Session),且单机故障会导致用户登录态丢失。

@Service
public class CartService {
    // 致命缺陷:状态绑定在单机内存中
    private final Map<String, List<Item>> localCartCache = new ConcurrentHashMap<>();

    public void addItem(String userId, Item item) {
        localCartCache.computeIfAbsent(userId, k -> new ArrayList<>()).add(item);
    }
}

✅ 正面示例:无状态 + 外部存储
服务本身无状态,状态下沉到 Redis 或数据库。任何一台服务器都可以处理任何用户的请求。

@Service
public class StatelessCartService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void addItem(String userId, Item item) {
        // 状态存储在共享的 Redis 中
        String key = "cart:" + userId;
        redisTemplate.opsForList().rightPush(key, item);
        // 服务实例可以随时增加或减少,不影响业务
    }
}

四、综合总结与检查清单

要编写同时具备“三高”特性的 Java 代码,建议在 Code Review 时对照以下清单:

维度 检查点 (Checklist) 推荐工具/库
高性能 [ ] 是否避免了循环内的对象创建?[ ] 集合是否预设了初始容量?[ ] 高并发计数是否使用了 LongAdder?[ ] 数据库查询是否有 N+1 问题?[ ] 是否利用了异步/非阻塞 I/O? JMH (基准测试), Async Profiler, Arthas
高可用 [ ] 所有 RPC/DB 调用是否有 Timeout?[ ] 是否有熔断和降级策略?[ ] 异常是否被合理捕获并记录,而不是吞掉或裸抛?[ ] 关键路径是否有重试机制(带退避)?[ ] 是否有健康检查接口 (/health)? Resilience4j, Sentinel, Hystrix (旧)
高扩展 [ ] 是否符合单一职责原则 (SRP)?[ ] 新增功能是否需要修改现有核心类 (OCP)?[ ] 服务是否无状态 (Stateless)?[ ] 模块间是否通过接口/事件解耦?[ ] 配置是否与代码分离? Spring Boot, DDD, Kafka/RocketMQ

五、进阶建议

  1. 拥抱虚拟线程 (Project Loom, Java 21+)

    • 传统线程模型下,高并发需要复杂的异步回调(Reactive),代码难读。
    • Java 21 的虚拟线程允许用同步的代码风格写出高并发的程序,极大地平衡了性能可维护性
    // Java 21+ 示例:成千上万个虚拟线程成本极低
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        IntStream.range(0, 10_000).forEach(i -> {
            executor.submit(() -> {
                Thread.sleep(Duration.ofSeconds(1)); // 阻塞不会浪费平台线程
                return i;
            });
        });
    }
    
  2. 可观测性 (Observability)

    • 没有监控的高性能代码是盲目的。必须集成 Metrics (Micrometer), Tracing (OpenTelemetry), Logging (Structured Logging)
    • 只有知道系统慢在哪里,才能优化性能;只有知道故障发生在哪里,才能保证可用性。
  3. 测试驱动

    • 单元测试保证逻辑正确(扩展性的基础)。
    • 压力测试 (JMeter/Gatling) 验证性能瓶颈。
    • 混沌工程 (Chaos Mesh) 验证高可用性(随机杀 Pod、模拟网络延迟)。

通过遵循上述原则和模式,你可以构建出既能在双11流量洪峰下稳如泰山,又能快速响应业务需求变化的健壮 Java 系统。

posted @ 2026-03-13 15:46  蓝迷梦  阅读(21)  评论(0)    收藏  举报