Java多线程更新Mellanox固件时出现重复操作同一设备问题
问题核心原因
你的代码中ProcessBuilder实例(也就是代码里的builder变量)是多线程共享的非线程安全对象,这是导致参数被覆盖的直接原因:
- 所有线程在
gate.await()之前都会调用builder.command()修改同一个实例的命令参数 CyclicBarrier会等所有线程都执行到gate.await()这行才一起放行,此时所有线程已经完成了builder.command()的调用,最后一轮循环的线程设置的参数会把前面所有线程的设置全部覆盖- 所以当所有线程同时执行
startProcess()时,调用的都是最后一个网卡、最后一个IP对应的命令,自然会出现4个线程同时操作同一台设备的情况,第一个抢占成功的线程执行完成,剩下3个因为设备被占用抛出报错,刚好和你遇到的现象完全匹配。
其他潜在问题
streamGobbler看起来也是全局共享的实例,多线程同时读取进程输出会导致结果混乱,甚至出现死锁、输出丢失的问题- 你的异常捕获逻辑里没有调用
latch.countDown(),如果某个线程执行报错,latch.await()会永远阻塞无法继续执行
修复方案
- 每个线程内部创建独立的
ProcessBuilder实例,不要共享全局实例,彻底避免参数被覆盖 - 每个线程配套创建独立的
streamGobbler实例处理对应进程的输出 - 异常捕获块中补充
latch.countDown()调用,避免程序永久阻塞
修复后代码示例
final CyclicBarrier gate = new CyclicBarrier(mellanox.size()+1); ArrayList<Thread> threads = new ArrayList<>(); CountDownLatch latch = new CountDownLatch(mellanox.size()); for (int index = 0; index < mellanox.size(); index++) { String mellanoxCard = mellanox.get(index); String ip = ipAddresses.get(index); Thread t = new Thread(() -> { try { if (isWindows) { // 每个线程创建独立的ProcessBuilder,不要用全局的builder ProcessBuilder localBuilder = new ProcessBuilder(); localBuilder.command(flint, "-d", mellanoxCard, "-i", mellanoxFWPath, "-y", "burn"); setupLoading.setProgressInfo("Updating Mellanox FW for System " + ip, "calibri"); gate.await(); // 这里传入localBuilder启动进程,或者修改startProcess方法接收ProcessBuilder作为参数 Process process = localBuilder.start(); // 每个线程创建独立的streamGobbler处理当前进程的输出 StreamGobbler localGobbler = new StreamGobbler(process.getInputStream()); localGobbler.start(); process.waitFor(); latch.countDown(); setupLoading.setProgressInfo(localGobbler.getResult(), "consolas"); } } catch (InterruptedException | BrokenBarrierException | IOException e) { setupLoading.setProgressInfo("An error has occured during Mellanox Firmware update!", "warning"); writeErrorLog(e.getMessage()); // 异常场景也减少计数,避免latch永久等待 latch.countDown(); } }); threads.add(t); } for(Thread t : threads) t.start(); try { gate.await(); latch.await(); } catch (InterruptedException | BrokenBarrierException e) { writeErrorLog(e.getMessage()); }
补充验证方式
如果你暂时不想改代码,可以在startProcess()执行前加一行日志,打印当前builder.command()的实际值,就能确认所有线程执行时用的参数是不是都是同一个。
内容的提问来源于stack exchange,提问作者Korlimann
相关产品推荐
相关产品推荐

