mcpField
C
concurrency-review

concurrency-review

github.com/decebals测试与质量761359 次安装7 周前更新
代码仓库

关于 concurrency-review

面向 Java 开发者的并发代码审查技能,帮助检查多线程代码。它结合常见缺陷清单,并涵盖现代 Java 并发特性和 Spring 异步处理陷阱。

主要功能

  1. 01检查竞态条件与可见性
  2. 02识别 Spring @Async 陷阱
  3. 03检查 CompletableFuture 错误与超时处理
  4. 04了解虚拟线程和结构化并发
  5. 05选择线程安全集合

适用场景

  1. 01审查共享状态的线程安全问题
  2. 02检查 Spring 异步方法
  3. 03评估 CompletableFuture 错误处理
  4. 04审查 Java 21/25 并发代码

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/decebals/claude-code-java --skill concurrency-review

安装后在 Claude Code 中输入/concurrency-review确认已加载。

附带文件README.md

详细介绍

并发审查技能

审查 Java 并发代码的正确性、安全性和现代最佳实践。

为什么重要

近 60% 的多线程应用会因共享资源管理不当而遇到问题。- ACM 研究

并发错误:

  • 难以复现 - 取决于时序
  • 难以测试 - 可能只在负载下出现
  • 难以调试 - 行为不确定

此技能有助于在问题进入生产环境之前发现它们。

何时使用

  • 审查包含 synchronized、volatile、Lock 的代码
  • 检查 @Async、CompletableFuture、ExecutorService
  • 验证共享状态的线程安全性
  • 审查虚拟线程 / 结构化并发代码
  • 任何由多个线程访问的代码

现代 Java(21/25):虚拟线程

何时使用虚拟线程
// ✅ Perfect for I/O-bound tasks (HTTP, DB, file I/O)
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Request request : requests) {
        executor.submit(() -> callExternalApi(request));
    }
}

// ❌ Not beneficial for CPU-bound tasks
// Use platform threads / ForkJoinPool instead

经验法则:如果应用从未同时运行 10,000 个以上的任务,虚拟线程可能不会带来显著收益。

Java 25:修复同步固定问题

在 Java 21-23 中,虚拟线程在进入包含阻塞操作的 synchronized 代码块时会变为“固定”状态。Java 25 修复了这个问题(JEP 491)。

// In Java 21-23: ⚠️ Could cause pinning
synchronized (lock) {
    blockingIoCall();  // Virtual thread pinned to carrier
}

// In Java 25: ✅ No longer an issue
// But consider ReentrantLock for explicit control anyway
使用 ScopedValue 替代 ThreadLocal
// ❌ ThreadLocal problematic with virtual threads
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

// ✅ ScopedValue (Java 21+ preview, improved in 25)
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();

ScopedValue.where(CURRENT_USER, user).run(() -> {
    // CURRENT_USER.get() available here and in child virtual threads
    processRequest();
});
结构化并发(Java 25 预览版)
// ✅ Structured concurrency - tasks tied to scope lifecycle
try (StructuredTaskScope.ShutdownOnFailure scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<User> userTask = scope.fork(() -> fetchUser(id));
    Subtask<Orders> ordersTask = scope.fork(() -> fetchOrders(id));

    scope.join();            // Wait for all
    scope.throwIfFailed();   // Propagate exceptions

    return new Profile(userTask.get(), ordersTask.get());
}
// All subtasks automatically cancelled if scope exits

Spring @Async 常见问题

1. 忘记添加 @EnableAsync
// ❌ @Async silently ignored
@Service
public class EmailService {
    @Async
    public void sendEmail(String to) { }
}

// ✅ Enable async processing
@Configuration
@EnableAsync
public class AsyncConfig { }
2. 在同一个类中调用异步方法
@Service
public class OrderService {

    // ❌ Bypasses proxy - runs synchronously!
    public void processOrder(Order order) {
        sendConfirmation(order);  // Direct call, not async
    }

    @Async
    public void sendConfirmation(Order order) { }
}

// ✅ Inject self or use separate service
@Service
public class OrderService {
    @Autowired
    private EmailService emailService;  // Separate bean

    public void processOrder(Order order) {
        emailService.sendConfirmation(order);  // Proxy call, async works
    }
}
3. 在非公共方法上使用 @Async
// ❌ Non-public methods - proxy can't intercept
@Async
private void processInBackground() { }

@Async
protected void processInBackground() { }

// ✅ Must be public
@Async
public void processInBackground() { }
4. 默认执行器会为每个任务创建一个线程
// ❌ Default SimpleAsyncTaskExecutor - creates new thread each time!
// Can cause OutOfMemoryError under load

// ✅ Configure proper thread pool
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
5. SecurityContext 不会传播
// ❌ SecurityContextHolder is ThreadLocal-bound
@Async
public void auditAction() {
    // SecurityContextHolder.getContext() is NULL here!
    String user = SecurityContextHolder.getContext().getAuthentication().getName();
}

// ✅ Use DelegatingSecurityContextAsyncTaskExecutor
@Bean
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    // ... configure ...
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

CompletableFuture 模式

错误处理
// ❌ Exception silently swallowed
CompletableFuture.supplyAsync(() -> riskyOperation());
// If riskyOperation throws, nobody knows

// ✅ Always handle exceptions
CompletableFuture.supplyAsync(() -> riskyOperation())
    .exceptionally(ex -> {
        log.error("Operation failed", ex);
        return fallbackValue;
    });

// ✅ Or use handle() for both success and failure
CompletableFuture.supplyAsync(() -> riskyOperation())
    .handle((result, ex) -> {
        if (ex != null) {
            log.error("Failed", ex);
            return fallbackValue;
        }
        return result;
    });
超时处理(Java 9+)
// ✅ Fail after timeout
CompletableFuture.supplyAsync(() -> slowOperation())
    .orTimeout(5, TimeUnit.SECONDS);  // Throws TimeoutException

// ✅ Return default after timeout
CompletableFuture.supplyAsync(() -> slowOperation())
    .completeOnTimeout(defaultValue, 5, TimeUnit.SECONDS);
组合 Future
// ✅ Wait for all
CompletableFuture.allOf(future1, future2, future3)
    .thenRun(() -> log.info("All completed"));

// ✅ Wait for first
CompletableFuture.anyOf(future1, future2, future3)
    .thenAccept(result -> log.info("First result: {}", result));

// ✅ Combine results
future1.thenCombine(future2, (r1, r2) -> merge(r1, r2));
使用合适的执行器
// ❌ CPU-bound task in ForkJoinPool.commonPool (default)
CompletableFuture.supplyAsync(() -> cpuIntensiveWork());

// ✅ Custom executor for blocking/I/O operations
ExecutorService ioExecutor = Executors.newFixedThreadPool(20);
CompletableFuture.supplyAsync(() -> blockingIoCall(), ioExecutor);

// ✅ In Java 21+, virtual threads for I/O
ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
CompletableFuture.supplyAsync(() -> blockingIoCall(), virtualExecutor);

经典并发问题

竞态条件:先检查后执行
// ❌ Race condition
if (!map.containsKey(key)) {
    map.put(key, computeValue());  // Another thread may have added it
}

// ✅ Atomic operation
map.computeIfAbsent(key, k -> computeValue());

// ❌ Race condition with counter
if (count < MAX) {
    count++;  // Read-check-write is not atomic
}

// ✅ Atomic counter
AtomicInteger count = new AtomicInteger();
count.updateAndGet(c -> c < MAX ? c + 1 : c);
可见性:缺少 volatile
// ❌ Other threads may never see the update
private boolean running = true;

public void stop() {
    running = false;  // May not be visible to other threads
}

public void run() {
    while (running) { }  // May loop forever
}

// ✅ Volatile ensures visibility
private volatile boolean running = true;
long/double 非原子性
// ❌ 64-bit read/write is non-atomic on 32-bit JVMs
private long counter;

public void increment() {
    counter++;  // Not atomic!
}

// ✅ Use AtomicLong or synchronization
private AtomicLong counter = new AtomicLong();

// ✅ Or volatile (for single-writer scenarios)
private volatile long counter;
双重检查锁定
// ❌ Broken without volatile
private static Singleton instance;

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();  // May be seen partially constructed
            }
        }
    }
    return instance;
}

// ✅ Correct with volatile
private static volatile Singleton instance;

// ✅ Or use holder class idiom
private static class Holder {
    static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
    return Holder.INSTANCE;
}
死锁:锁定顺序
// ❌ Potential deadlock
// Thread 1: lock(A) -> lock(B)
// Thread 2: lock(B) -> lock(A)

public void transfer(Account from, Account to, int amount) {
    synchronized (from) {
        synchronized (to) {
            // Transfer logic
        }
    }
}

// ✅ Consistent lock ordering
public void transfer(Account from, Account to, int amount) {
    Account first = from.getId() < to.getId() ? from : to;
    Account second = from.getId() < to.getId() ? to : from;

    synchronized (first) {
        synchronized (second) {
            // Transfer logic
        }
    }
}

线程安全集合

选择合适的集合
使用场景错误选择正确选择
并发读写HashMapConcurrentHashMap
频繁迭代ConcurrentHashMapCopyOnWriteArrayList
生产者-消费者ArrayListBlockingQueue
并发排序TreeMapConcurrentSkipListMap
ConcurrentHashMap 常见问题
// ❌ Non-atomic compound operation
if (!map.containsKey(key)) {
    map.put(key, value);
}

// ✅ Atomic
map.putIfAbsent(key, value);
map.computeIfAbsent(key, k -> createValue());

// ❌ Nested compute can deadlock
map.compute(key1, (k, v) -> {
    return map.compute(key2, ...);  // Deadlock risk!
});

并发审查清单

🔴 高严重性(很可能是错误)
  • 对共享状态执行先检查后操作时,必须进行同步
  • synchronized 代码块中不得调用外部或未知代码(有死锁风险)
  • 双重检查锁定中存在 volatile
  • 循环等待更新时,不读取非 volatile 字段
  • ConcurrentHashMap.compute() 不调用其他映射操作
  • @Async 方法为公共方法,且从不同的 Bean 中调用
🟡 中等严重性(潜在问题)
  • 线程池大小设置合理且已命名
  • 已处理 CompletableFuture 异常(exceptionally/handle)
  • 如有需要,已将 SecurityContext 传播到异步任务
  • 已正确关闭 ExecutorService
  • 在 finally 块中调用 Lock.unlock()
  • 共享数据使用线程安全集合
🟢 现代模式(Java 21/25)
  • 对 I/O 密集型并发任务使用虚拟线程
  • 考虑使用 ScopedValue 替代 ThreadLocal
  • 对相关子任务使用结构化并发
  • 为 CompletableFuture 操作设置超时
📝 文档
  • 在共享类上注明线程安全性
  • 记录嵌套锁的加锁顺序
  • 说明每处 volatile 用法的理由

分析命令

# Find synchronized blocks
grep -rn "synchronized" --include="*.java"

# Find @Async methods
grep -rn "@Async" --include="*.java"

# Find volatile fields
grep -rn "volatile" --include="*.java"

# Find thread pool creation
grep -rn "Executors\.\|ThreadPoolExecutor\|ExecutorService" --include="*.java"

# Find CompletableFuture without error handling
grep -rn "CompletableFuture\." --include="*.java" | grep -v "exceptionally\|handle\|whenComplete"

# Find ThreadLocal (consider ScopedValue in Java 21+)
grep -rn "ThreadLocal" --include="*.java"

评论

支持 Markdown