Java区块链应用ExecutorService提交挖矿任务返回相同区块问题求助
问题根因
- 核心是并发任务提交逻辑错误导致所有挖矿任务都基于创世块计算:你在循环里一次性把所有
NO_OF_BLOCKS个挖矿任务全部提交到线程池,所有任务启动时区块链中只有创世块,调用blockchain.getBlocks().peekLast()拿到的都是创世块实例,因此生成的新块ID全部是创世块ID+1=1,prevHash也全部是创世块的哈希值0,和你贴出的日志特征完全匹配。 - 第二个隐含问题是区块链操作没有做线程同步:即使后续有任务成功添加了新块,其他线程也可能因为内存可见性问题,无法读取到最新的链尾块,依然基于旧的创世块生成无效区块。
- 第三个问题是区块添加逻辑没有做原子校验:当多个矿工同时生成ID为1的区块时,只有第一个提交的区块能通过链合法性校验添加到链上,其余区块的prevHash和当前最新块哈希不匹配,会被
addNewBlock抛出异常丢弃,因此始终只有ID为1的区块被打印、没有更高ID的区块生成。
可行解决方案
方案1:模拟真实公链的多矿工竞争挖矿逻辑(推荐)
保留多线程竞争挖矿的特性,调整任务调度逻辑:
- 每次仅启动一批线程竞争挖当前最新高度的区块,不要一次性提交所有高度的任务
- 对区块链的读写操作加锁,保证
获取链尾块→校验区块合法性→添加新区块的整个流程原子执行,避免并发冲突 - 成功挖出一个区块后,再触发下一个高度的挖矿任务,直到生成的区块总数达到目标值
- 某条线程成功挖出有效区块后,主动中断同批次其他正在挖矿的线程,释放计算资源
参考修改后的核心代码:
// 需加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
相关产品推荐
相关产品推荐

