concurrency-review
3
总安装量
3
周安装量
#60229
全站排名
安装命令
npx skills add https://github.com/decebals/claude-code-java --skill concurrency-review
Agent 安装分布
qoder
3
claude-code
3
github-copilot
3
roo
3
codex
3
kimi-cli
3
Skill 文档
Concurrency Review Skill
Review Java concurrent code for correctness, safety, and modern best practices.
Why This Matters
Nearly 60% of multithreaded applications encounter issues due to improper management of shared resources. – ACM Study
Concurrency bugs are:
- Hard to reproduce – timing-dependent
- Hard to test – may only appear under load
- Hard to debug – non-deterministic behavior
This skill helps catch issues before they reach production.
When to Use
- Reviewing code with
synchronized,volatile,Lock - Checking
@Async,CompletableFuture,ExecutorService - Validating thread safety of shared state
- Reviewing Virtual Threads / Structured Concurrency code
- Any code accessed by multiple threads
Modern Java (21/25): Virtual Threads
When to Use Virtual Threads
// â
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
Rule of thumb: If your app never has 10,000+ concurrent tasks, virtual threads may not provide significant benefit.
Java 25: Synchronized Pinning Fixed
In Java 21-23, virtual threads became “pinned” when entering synchronized blocks with blocking operations. Java 25 fixes this (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 Over 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();
});
Structured Concurrency (Java 25 Preview)
// â
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 Pitfalls
1. Forgetting @EnableAsync
// â @Async silently ignored
@Service
public class EmailService {
@Async
public void sendEmail(String to) { }
}
// â
Enable async processing
@Configuration
@EnableAsync
public class AsyncConfig { }
2. Calling Async from Same Class
@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 on Non-Public Methods
// â Non-public methods - proxy can't intercept
@Async
private void processInBackground() { }
@Async
protected void processInBackground() { }
// â
Must be public
@Async
public void processInBackground() { }
4. Default Executor Creates Thread Per Task
// â 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 Not Propagating
// â 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 Patterns
Error Handling
// â 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;
});
Timeout Handling (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);
Combining Futures
// â
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));
Use Appropriate Executor
// â 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);
Classic Concurrency Issues
Race Conditions: Check-Then-Act
// â 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);
Visibility: Missing 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;
Non-Atomic 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;
Double-Checked Locking
// â 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;
}
Deadlocks: Lock Ordering
// â 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
}
}
}
Thread-Safe Collections
Choose the Right Collection
| Use Case | Wrong | Right |
|---|---|---|
| Concurrent reads/writes | HashMap |
ConcurrentHashMap |
| Frequent iteration | ConcurrentHashMap |
CopyOnWriteArrayList |
| Producer-consumer | ArrayList |
BlockingQueue |
| Sorted concurrent | TreeMap |
ConcurrentSkipListMap |
ConcurrentHashMap Pitfalls
// â 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!
});
Concurrency Review Checklist
ð´ High Severity (Likely Bugs)
- No check-then-act on shared state without synchronization
- No
synchronizedcalling external/unknown code (deadlock risk) -
volatilepresent for double-checked locking - Non-volatile fields not read in loops waiting for updates
-
ConcurrentHashMap.compute()doesn’t call other map operations - @Async methods are public and called from different beans
ð¡ Medium Severity (Potential Issues)
- Thread pools properly sized and named
- CompletableFuture exceptions handled (exceptionally/handle)
- SecurityContext propagated to async tasks if needed
-
ExecutorServiceproperly shut down -
Lock.unlock()in finally block - Thread-safe collections used for shared data
ð¢ Modern Patterns (Java 21/25)
- Virtual threads used for I/O-bound concurrent tasks
- ScopedValue considered over ThreadLocal
- Structured concurrency for related subtasks
- Timeouts on CompletableFuture operations
ð Documentation
- Thread safety documented on shared classes
- Locking order documented for nested locks
- Each
volatileusage justified
Analysis Commands
# 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"