You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java自定义Barrier实现遭遇死锁,请求技术分析与解决

嘿,我仔细看了你写的自定义Barrier实现,这个死锁问题其实是并发编程里很常见的竞态条件导致的,咱们一步步拆解问题,再给你修复方案:

死锁原因分析

你的代码里有几个核心问题直接导致了死锁:

  • 非原子的计数操作:counter++不是原子操作,它会被拆分成「读取当前值→加1→写回新值」三个步骤。当多个线程同时执行这个操作时,会出现竞态条件——比如两个线程同时读到counter=4,各自加1后写回,最终counter还是5,但实际上有两个线程都认为自己是最后一个到达的;更糟的情况是,多个线程的更新互相覆盖,导致counter永远达不到toWait的数值,最后所有线程都卡在barrier.acquire()上,没人释放信号量许可,直接死锁。
  • 无同步保护的条件判断:即使你后续补全了if (counter == toWait)的释放逻辑,因为counter的更新没有同步保护,这个判断也会出现线程安全问题,可能永远触发不了释放逻辑,或者重复触发(虽然Semaphore允许多次release,但这里主要问题是触发不了)。
  • static变量的共享风险:虽然barrier、counter、toWait用static是为了让所有Barrier实例共享,但没有同步机制的共享,只会放大线程安全问题。
修复后的实现方案

我给你两种修复后的版本,你可以根据需求选择:

方案一:用synchronized同步块保护计数逻辑

这种方式简单直观,适合入门理解:

public class Barrier implements Runnable {
    private static final Semaphore barrier = new Semaphore(0);
    private static final int toWait = 5;
    private static int counter = 0;
    private static long sleepTime;
    public static int ID = 0;
    private final int id = ++ID;

    public Barrier(long sleep) {
        sleepTime = sleep;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(sleepTime);
            System.out.println("线程" + id + "到达屏障");

            // 用类对象作为锁,保护counter的更新和条件判断
            synchronized (Barrier.class) {
                counter++;
                // 只有最后一个到达的线程释放所有许可
                if (counter == toWait) {
                    barrier.release(toWait);
                    System.out.println("最后一个线程到达,释放所有许可");
                    // 重置计数器,支持Barrier重复使用
                    counter = 0;
                }
            }

            // 等待屏障放行
            barrier.acquire();
            System.out.println("线程" + id + "通过屏障");

        } catch (InterruptedException e) {
            // 重新设置中断状态,避免中断信号丢失
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

方案二:用AtomicInteger实现原子计数(更高效)

如果追求更高的性能,可以用JUC包的原子类来避免锁竞争:

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

public class Barrier implements Runnable {
    private static final Semaphore barrier = new Semaphore(0);
    private static final int toWait = 5;
    private static final AtomicInteger counter = new AtomicInteger(0);
    private static long sleepTime;
    public static int ID = 0;
    private final int id = ++ID;

    public Barrier(long sleep) {
        sleepTime = sleep;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(sleepTime);
            System.out.println("线程" + id + "到达屏障");

            // 原子递增计数,返回递增前的旧值
            int currentCount = counter.getAndIncrement();
            // 当旧值等于toWait-1时,说明当前线程是最后一个到达的
            if (currentCount == toWait - 1) {
                barrier.release(toWait);
                System.out.println("最后一个线程到达,释放所有许可");
                // 重置计数器,支持重复使用
                counter.set(0);
            }

            // 等待屏障放行
            barrier.acquire();
            System.out.println("线程" + id + "通过屏障");

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}
额外提示
  • 如果你希望这个Barrier可以重复使用,一定要在释放许可后重置计数器(上面的代码已经做了)。
  • sleepTime是static的,如果你想让不同的Barrier实例有不同的休眠时间,应该把它改成非static成员变量,不然后面创建的实例会覆盖前面的sleepTime值。
  • 处理InterruptedException时,重新设置线程的中断状态是个好习惯,避免中断信号被吞掉导致后续逻辑异常。

内容的提问来源于stack exchange,提问作者Rango

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:02:39