{"record":{"id":"a400ed082af9574d","repo":"crossoverJie/JCSprout","slug":"concurrent-error","errorCode":null,"errorMessage":"concurrent error","messagePattern":"concurrent error","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/crossoverjie/concurrent/communication/MultipleThreadCountDownKit.java","lineNumber":54,"sourceCode":"     * @param notify\n     */\n    public void setNotify(Notify notify){\n        notifyListen = notify ;\n    }\n\n\n    /**\n     * 线程完成后计数 -1\n     */\n    public void countDown(){\n\n        if (counter.get() <= 0){\n            return;\n        }\n\n        int count = this.counter.decrementAndGet();\n        if (count < 0){\n            throw new RuntimeException(\"concurrent error\") ;\n        }\n\n        if (count == 0){\n            synchronized (notify){\n                notify.notify();\n            }\n        }\n\n    }\n\n    /**\n     * 等待所有的线程完成\n     * @throws InterruptedException\n     */\n    public void await() throws InterruptedException {\n        synchronized (notify){\n            while (counter.get() > 0){\n                notify.wait();","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/crossoverJie/JCSprout/blob/fc4c6e5f6d1772c2aec4c0f94cb3c8e7eb04018f/src/main/java/com/crossoverjie/concurrent/communication/MultipleThreadCountDownKit.java#L36-L72","documentation":"Thrown in countDown() when the AtomicInteger counter goes below zero after decrementAndGet(). This is a check-then-act race: when counter is 1, two concurrent threads can both pass the counter.get() <= 0 guard (it reads 1, which is > 0), then both decrement — one reaches 0, the other reaches -1 and triggers the error. The guard is not atomic with the decrement.","triggerScenarios":"Two or more threads call countDown() simultaneously when the counter is at 1 (or generally when more threads call countDown than the remaining count). The get() check and the decrementAndGet() are separate operations with a window between them.","commonSituations":"High-convergence point in a parallel pipeline where multiple workers finish at nearly the same instant. Reusing the same latch object for a second wave of countDown calls after it already reached zero. Counting down more times than the initial count due to a logic error in the caller.","solutions":["Replace the check-then-decrement with an atomic CAS loop: spin on compareAndSet until you decrement from a positive value, returning early if the value is already 0.","Use java.util.concurrent.CountDownLatch instead, which handles this atomically and is battle-tested.","If you must keep this class, ensure the number of countDown() calls never exceeds the initial count by auditing all call sites."],"exampleFix":"// before (race between get() and decrementAndGet())\nif (counter.get() <= 0) { return; }\nint count = counter.decrementAndGet();\nif (count < 0) { throw new RuntimeException(\"concurrent error\"); }\n\n// after (atomic CAS loop — no window)\nint current;\ndo {\n    current = counter.get();\n    if (current <= 0) { return; }\n} while (!counter.compareAndSet(current, current - 1));\nif (current - 1 == 0) {\n    synchronized (notify) { notify.notify(); }\n}","handlingStrategy":"try-catch","validationCode":"// Ensure the number of countDown() calls never exceeds the initial count.\n// Track calls yourself if the calling code is complex:\nAtomicInteger downCalls = new AtomicInteger(0);\nvoid safeCountDown(MultipleThreadCountDownKit latch, int initial) {\n    if (downCalls.incrementAndGet() > initial) {\n        LOGGER.warn(\"countDown called more times than initial count\");\n        return;\n    }\n    latch.countDown();\n}","typeGuard":null,"tryCatchPattern":"try {\n    latch.countDown();\n} catch (RuntimeException e) {\n    if (\"concurrent error\".equals(e.getMessage())) {\n        // Lost a race in the non-atomic check-then-decrement; safe to ignore\n        // since the latch has already reached zero.\n        LOGGER.debug(\"countDown race: latch already at zero\", e);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Prefer java.util.concurrent.CountDownLatch over this class — it handles concurrent countDown atomically.","Ensure the total number of countDown() calls exactly matches the initial count passed to the constructor.","Do not reuse a latch after it has reached zero; create a new instance for the next batch."],"tags":["concurrency","race-condition","countdown","atomic"],"backgroundTag":null,"analyzedSha":"fc4c6e5f6d1772c2aec4c0f94cb3c8e7eb04018f","analyzedAt":"2026-08-14T05:43:20.992Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}