Install
$ agentstack add skill-mickeyyaya-refactoring-skills-concurrency-patterns ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Concurrency Patterns for Code Review
Overview
Concurrency bugs are among the hardest to reproduce: they appear non-deterministically, are invisible in unit tests, and can cause data corruption in production. Use this guide during code review to catch concurrency hazards before they ship.
When to Use
- Reviewing code that accesses shared state from multiple threads or coroutines
- Reviewing async/await code for hidden bottlenecks or error-swallowing
- Evaluating background task queues, worker pools, or message-passing systems
- Code touching databases, caches, or files from concurrent request handlers
Quick Reference
| Pattern | Core Idea | Primary Red Flag | |---------|-----------|-----------------| | Race Conditions | Multiple threads read-modify-write shared state | Unsynchronized count++, check-then-act without lock | | Deadlocks | Circular wait on two or more locks | Nested lock acquisition in inconsistent order | | Thread Safety | Ensuring shared resources are safe to use concurrently | Mutable fields accessed without synchronization | | Immutable Data | Eliminate races by sharing only read-only values | Passing mutable objects across thread boundaries | | Producer-Consumer | Decouple work creation from execution via queue | Unbounded queue, missing backpressure | | Actor Model | Isolated state, message-only communication | Shared mutable state between actors, blocking in actor | | Thread Pool | Reuse threads instead of creating per-request | Thread-per-request at scale, pool exhaustion | | Async/Await Pitfalls | Common mistakes in async code | Fire-and-forget without error handler, blocking in async | | Read-Write Locks | Multiple readers OR single writer | Write-heavy workload using RWLock (more overhead than mutex) | | Compare-and-Swap | Lock-free atomic update via CAS loop | ABA problem, spin loop without backoff |
Patterns in Detail
1. Race Conditions
Red Flags:
- Unsynchronized
count++— read-modify-write is three operations, not one - Check-then-act without lock:
if (!map.containsKey(k)) { map.put(k, v); } - Lazy init without synchronization:
if (instance == null) instance = new Foo() - Multiple fields updated separately when they should be atomic
TypeScript:
// BEFORE — read-modify-write is not atomic across workers
let activeConnections = 0;
function onConnect() { activeConnections++; }
// AFTER — Atomics on SharedArrayBuffer
const counter = new Int32Array(new SharedArrayBuffer(4));
function onConnect() { Atomics.add(counter, 0, 1); }
Java: private final AtomicInteger count = new AtomicInteger(0); Go: var count atomic.Int64 / count.Add(1)
2. Deadlocks
Red Flags:
- Nested lock acquisition in different orders across two functions
- Lock held during I/O, network, or DB calls (long hold time = high contention)
synchronized/lock()inside callbacks that may already hold a lock- Missing
finally/deferto release locks on exceptions
Java:
// BEFORE — Thread A locks `from` first, Thread B may lock `to` first
void transfer(Account from, Account to, int amount) {
synchronized (from) { synchronized (to) { /* race */ } }
}
// AFTER — always lock in consistent order (by ID)
void transfer(Account from, Account to, int amount) {
Account first = from.id cache = new HashMap<>();
public User getUser(String id) {
if (!cache.containsKey(id)) cache.put(id, loadFromDB(id));
return cache.get(id);
}
// AFTER — computeIfAbsent is atomic
private final ConcurrentHashMap cache = new ConcurrentHashMap<>();
public User getUser(String id) { return cache.computeIfAbsent(id, this::loadFromDB); }
Python:
class UserCache:
def __init__(self) -> None:
self._lock = threading.Lock()
self._cache: dict[str, User] = {}
def get_user(self, user_id: str) -> User:
with self._lock:
if user_id not in self._cache:
self._cache[user_id] = load_from_db(user_id)
return self._cache[user_id]
4. Immutable Data
Red Flags:
- Passing mutable collections between threads without copying
setX()/setY()mutators on objects shared across goroutines or thread pools- Config objects built once but exposing mutation methods
- Java classes missing
finalon fields that should never change
TypeScript:
// BEFORE — mutable config shared with workers; mutations cause races
const config = { maxRetries: 3, timeout: 5000 };
workerPool.start(config);
config.timeout = 10000; // workers may be reading this
// AFTER — freeze creates a read-only snapshot
const config = Object.freeze({ maxRetries: 3, timeout: 5000 } as const);
workerPool.start(config);
Java: record Config(int maxRetries, int timeoutMs) {} (all fields final, no setters) Go: func startWorker(cfg Config) { /* cfg is a copy — pass by value */ }
Cross-reference: refactor-functional-patterns — Immutability section for array/object patterns.
5. Producer-Consumer
Red Flags:
- Unbounded queue (
new LinkedBlockingQueue<>()with no capacity) — OOM under load - No backpressure: producer blocks or drops silently when consumer is slow
- Consumer swallowing exceptions — tasks silently lost
- Queue depth not monitored
Go:
// BEFORE — unbounded channel, OOM risk
tasks := make(chan Task)
// AFTER — bounded channel with backpressure
const maxQueue = 1000
tasks := make(chan Task, maxQueue)
func produce(t Task) error {
select {
case tasks queue = new LinkedBlockingQueue<>(1000);
if (!queue.offer(task, 100, TimeUnit.MILLISECONDS))
throw new RejectedExecutionException("Queue full");
6. Actor Model
Red Flags:
- Actors accessing shared mutable objects directly (bypassing messages)
- Blocking calls inside actor message handler — starves other actors
- Unbounded mailbox (same risk as unbounded queue)
- Missing state transitions — actors handling messages in wrong states
TypeScript:
type Msg = { type: 'increment' } | { type: 'get'; reply: (n: number) => void };
class CounterActor {
private count = 0;
private mailbox: Msg[] = [];
private running = false;
send(msg: Msg) { this.mailbox.push(msg); if (!this.running) this.drain(); }
private drain() {
this.running = true;
while (this.mailbox.length) {
const msg = this.mailbox.shift()!;
if (msg.type === 'increment') this.count++; else msg.reply(this.count);
}
this.running = false;
}
}
Python (asyncio queue as mailbox):
class CounterActor:
def __init__(self) -> None:
self._count = 0; self._queue: asyncio.Queue[tuple] = asyncio.Queue()
async def run(self) -> None:
while True:
msg, fut = await self._queue.get()
if msg == 'increment': self._count += 1; fut.set_result(None)
elif msg == 'get': fut.set_result(self._count)
7. Thread Pool / Worker Pool
Red Flags:
new Thread(task).start()in request handler — unbounded thread creationExecutors.newCachedThreadPool()under bursty load — unlimited threads- Pool size hard-coded without justification (should depend on CPU count or I/O ratio)
- Tasks submitted to a pool that block waiting for another pool task (starvation deadlock)
- No shutdown hook — threads keep JVM alive after main exits
Java:
// BEFORE — one OS thread per request, unbounded
new Thread(() -> handleRequest(req)).start();
// AFTER — fixed pool with sized based on workload
ExecutorService pool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2 // CPU-bound: 1x; IO-bound: 2x+
);
Future future = pool.submit(() -> handleRequest(req));
Go:
func newWorkerPool(workers int, jobs {
const users: User[] = [];
for (const id of ids) { users.push(await fetchUser(id)); }
return users;
}
// AFTER — parallel
async function loadAll(ids: readonly string[]): Promise {
return Promise.all(ids.map(id => fetchUser(id)));
}
// Fire-and-forget — WRONG vs. CORRECT
cleanupExpiredSessions(); // wrong
cleanupExpiredSessions().catch(err => logger.error('Cleanup', { err })); // correct
Python:
async def load_all(ids: list[str]) -> list[User]:
return await asyncio.gather(*[fetch_user(uid) for uid in ids])
9. Read-Write Locks
Red Flags:
RWLockin write-heavy workload — overhead exceeds gain; use plainMutex- Write lock held during I/O
- Upgrading read lock to write lock without release — deadlock risk
RLock/RUnlockmismatches on error paths
Go:
type SafeMap struct { mu sync.RWMutex; m map[string]string }
func (s *SafeMap) Get(key string) (string, bool) {
s.mu.RLock(); defer s.mu.RUnlock()
return s.m[key]
}
func (s *SafeMap) Set(key, value string) {
s.mu.Lock(); defer s.mu.Unlock()
s.m[key] = value
}
Java:
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public String get(String key) {
rwLock.readLock().lock();
try { return map.get(key); } finally { rwLock.readLock().unlock(); }
}
public void put(String key, String value) {
rwLock.writeLock().lock();
try { map.put(key, value); } finally { rwLock.writeLock().unlock(); }
}
10. Compare-and-Swap (CAS)
Red Flags:
- ABA problem: value changes A->B->A; CAS succeeds but state changed meaningfully (use stamped references)
- Unbounded spin loop without backoff — CPU waste, livelock risk
- CAS for multi-field updates — CAS is single-variable; multi-field needs lock or versioned snapshot
Java:
AtomicReference stateRef = new AtomicReference<>(initialState);
void updateState(UnaryOperator transform) {
State current, next;
do {
current = stateRef.get();
next = transform.apply(current);
} while (!stateRef.compareAndSet(current, next));
}
Go (yield between retries to prevent livelock):
var value atomic.Int64
func increment() {
for {
old := value.Load()
if value.CompareAndSwap(old, old+1) { return }
runtime.Gosched()
}
}
Concurrency Anti-Patterns
| Anti-Pattern | Description | Fix | |-------------|-------------|-----| | Double-checked locking | Broken without volatile in Java | Use volatile + double-check (Java 5+), once.Do (Go), or static initializers | | Lock held during I/O | Blocks all threads for full I/O latency | Load data outside lock; swap reference under brief lock | | Thread-per-connection | Exhausted at ~10k connections | Non-blocking I/O with thread pool or async event loop | | Swallowed InterruptedException | Breaks cooperative cancellation | Re-interrupt: Thread.currentThread().interrupt() | | Thread.sleep() for sync | Sleeping to wait for another thread | Use CountDownLatch, CompletableFuture, WaitGroup, or channel | | Async void / fire-and-forget | Exceptions silently swallowed | Always attach .catch() / .add_done_callback() / store Future | | Closure over mutable loop var | All closures share same variable | Capture a copy: final int copy = i; |
Double-checked locking — Java fix:
// WRONG — without volatile, JIT may publish partially-constructed object
private static Singleton instance;
// CORRECT — volatile ensures visibility before reference published
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}}
return instance;
}
Cross-References
refactor-functional-patterns— Immutability: use immutable data to eliminate synchronizationreview-code-quality-process— Async errors and interrupted exceptions in broader error-handling reviewreview-solid-clean-code— SRP: separating I/O from computation makes concurrency boundaries explicitdetect-code-smells— "Shotgun Surgery"/"Feature Envy" indicate state that should be behind a single owner
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mickeyyaya
- Source: mickeyyaya/refactoring-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.