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

Java区块链应用ExecutorService提交挖矿任务返回相同区块问题求助

问题根因

  • 核心是并发任务提交逻辑错误导致所有挖矿任务都基于创世块计算:你在循环里一次性把所有NO_OF_BLOCKS个挖矿任务全部提交到线程池,所有任务启动时区块链中只有创世块,调用blockchain.getBlocks().peekLast()拿到的都是创世块实例,因此生成的新块ID全部是创世块ID+1=1,prevHash也全部是创世块的哈希值0,和你贴出的日志特征完全匹配。
  • 第二个隐含问题是区块链操作没有做线程同步:即使后续有任务成功添加了新块,其他线程也可能因为内存可见性问题,无法读取到最新的链尾块,依然基于旧的创世块生成无效区块。
  • 第三个问题是区块添加逻辑没有做原子校验:当多个矿工同时生成ID为1的区块时,只有第一个提交的区块能通过链合法性校验添加到链上,其余区块的prevHash和当前最新块哈希不匹配,会被addNewBlock抛出异常丢弃,因此始终只有ID为1的区块被打印、没有更高ID的区块生成。

可行解决方案

方案1:模拟真实公链的多矿工竞争挖矿逻辑(推荐)

保留多线程竞争挖矿的特性,调整任务调度逻辑:

  1. 每次仅启动一批线程竞争挖当前最新高度的区块,不要一次性提交所有高度的任务
  2. 对区块链的读写操作加锁,保证获取链尾块→校验区块合法性→添加新区块的整个流程原子执行,避免并发冲突
  3. 成功挖出一个区块后,再触发下一个高度的挖矿任务,直到生成的区块总数达到目标值
  4. 某条线程成功挖出有效区块后,主动中断同批次其他正在挖矿的线程,释放计算资源

参考修改后的核心代码:

// 需加volatile保证线程可见性,或用AtomicInteger
private volatile int currentBlockCount = 1;
private final Object lock = new Object();

public void executeCommand(int noOfStartingZero) throws ExecutionException, InterruptedException {
    // 第一轮先提交挖矿任务,挖第1个块
    submitMiningTask(noOfStartingZero);
}

private void submitMiningTask(int noOfStartingZero) {
    // 每次提交N个矿工线程竞争挖当前高度的块,N为你想设置的并发矿工数
    int minerCount = 5;
    for (int i = 0; i < minerCount; i++) {
        executor.submit(() -> {
            Block prevBlock;
            synchronized (lock) {
                prevBlock = this.blockchain.getBlocks().peekLast();
            }
            Block block = this.minerCommand.mine(prevBlock, noOfStartingZero);
            System.out.println("block: " + block);
            block.setMinedBy(Thread.currentThread().getName());
            
            boolean addSuccess = false;
            synchronized (lock) {
                try {
                    // addNewBlock内部要校验prevHash等于当前链尾块的hash,校验通过才添加
                    this.blockchain.addNewBlock(block);
                    addSuccess = true;
                    currentBlockCount++;
                } catch (InvalidBlockException e) {
                    // 校验失败说明已经有其他矿工先挖出了当前高度的块,直接丢弃
                }
            }
            
            // 挖矿成功且还没到目标块数,提交下一轮挖矿任务
            if (addSuccess && currentBlockCount <= NO_OF_BLOCKS) {
                // 先中断当前所有正在运行的挖矿线程,避免资源浪费
                executor.shutdownNow();
                // 重建线程池提交下一轮任务
                executor = Executors.newFixedThreadPool(minerCount);
                submitMiningTask(noOfStartingZero);
            }
        });
    }
}

方案2:简单顺序挖矿(适合测试场景)

如果不需要模拟多矿工竞争,直接按顺序挖块即可,逻辑更简单:

public void executeCommand(int noOfStartingZero) throws ExecutionException, InterruptedException {
    for (int i = 0; i < NO_OF_BLOCKS; i++) {
        // 提交单个挖矿任务,等它执行完成再提交下一个
        executor.submit(() -> {
            Block prevBlock = this.blockchain.getBlocks().peekLast();
            Block block = this.minerCommand.mine(prevBlock, noOfStartingZero);
            System.out.println("block: " + block);
            block.setMinedBy(Thread.currentThread().getName());
            try {
                this.blockchain.addNewBlock(block);
            } catch (InvalidBlockException e) {
                e.printStackTrace();
            }
        }).get(); // 加get()等待当前任务执行完成再走下一轮循环
    }
    executor.shutdown();
}

内容的提问来源于stack exchange,提问作者Nam V. Do

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 06:24:05