Java线程同步问题

这是JetBrains Academy的任务之一。 我们必须实现BlockChain类,该类必须支持将新块添加到我们的区块链并检查区块链的身份(以防万一有人从外部更改了区块链块的任何字段-为了进行仿真,我添加了changeBlockVal方法)。

每个块都包含自己的哈希值和上一个块的哈希值。为了使查找新块的哈希值的过程更加困难,哈希值必须以一定数量的零开始。因此,我添加了一个名为magic的字段,因此您可以随机更改此字段的值,直到找到其中包含适当数量的零的哈希值为止。零的数量也在变化-在添加每个新块之后,我们分析其创建时间,并相应地更改零的数量。

在main方法中,我创建了几个线程,并给他们一个添加15个块的任务。为了避免内存一致性错误,我已经同步了这些线程-矿工仍然可以同时计算新块,但事实是他们不能同时将新块添加到我们的区块链中。在此实现中,矿工独立工作,这意味着他们没有不同的输入范围来查找魔术数字。另外,在将新块添加到我们的区块链之前,我会在添加方法的关键部分检查新块的真实性。如果该块不可靠(例如,如果某个其他线程较早地添加了他的新块),则当前线程仅递归调用add方法。

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Main {
    public static void main(String[] args) throws {
        BlockChain chain = new BlockChain();

        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        for (int i = 0; i < 15; i++) {
            executor.submit(() -> chain.add());
        }

        executor.shutdown();
    }
}

class BlockChain {
    private Vector<Block> chain;
    private String lastBlockHash;
    private int zerosNumber;
    private int t = 1;
    boolean zerosChangeSign;

    public BlockChain() {
        zerosNumber = 0;

        chain = new Vector<>();
        lastBlockHash = "";
    }

    public void add() {
        try {
            Block newBlock = new Block(chain.lastElement().id + 1, lastBlockHash, zerosNumber);

            synchronized (chain) {
                if (Block.areConsequentBlocks(chain.lastElement(), newBlock, zerosNumber)) {
                    chain.add(newBlock);
                    updateComplexity(newBlock.creatingTime);
                    printBlock(newBlock);
                } else {
                    add();
                }
            }
        } catch (NoSuchElementException e) {
            Block newBlock = new Block(1, "0", zerosNumber);

            synchronized (chain) {
                if (chain.isEmpty()) {
                    chain.add(newBlock);
                    updateComplexity(newBlock.creatingTime);
                    printBlock(newBlock);
                } else {
                    add();
                }
            }
        } finally {
            lastBlockHash = chain.lastElement().getHash();
        }
    }

    public void updateComplexity(long time) {
        if ((time / 1000 - 7) < 0) {
            zerosNumber += t;
            zerosChangeSign = true;
        } else {
            zerosNumber -= t;
            zerosChangeSign = false;
        }
    }

    public boolean checkChain() {
        Object[] arr = chain.toArray();

        for (int i = 1; i < arr.length; i++) {
            Block prev = (Block) arr[i - 1];
            Block curr = (Block) arr[i];

            if (!prev.getHash().equals(curr.prevHash)) return false;
        }

        try {
            return chain.lastElement().getHash().equals(lastBlockHash);
        } catch (Exception e) {
            System.out.println("Blockchain is empty!");
            return true;
        }
    }

    public void changeBlockVal(int id) {
        try {
            chain.get(id).id = (new Random().nextInt(24)) - 100;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public void printBlock(Block val) {
        System.out.println("Block:\n" +
                "Created by miner # " + Thread.currentThread().getId() + "\n" +
                "Id: " + val.id + "\n" +
                "Timestamp: " + val.timeStamp + "\n" +
                "Magic number: " + val.magic + "\n" +
                "Hash of the previous block:\n" + val.prevHash + "\n" +
                "Hash of the block:\n" + val.hash + "\n" +
                "Block was generating for " + (val.creatingTime / 1000) + " seconds" + "\n" +
                "N was " + (zerosChangeSign ? "increased" : "decreased") + " to " + zerosNumber + "\n");
    }
}

class Block {
    int id;
    long timeStamp;
    long timeOfCreating;
    long creatingTime;
    String hash;
    String prevHash;
    int magic = 0;
    private int zerosNumber;

    public Block(int i, String hash, int n) {
        timeOfCreating = new Date().getTime();
        id = i;
        zerosNumber = n;
        prevHash = hash;
        this.hash = createMagicHash();
        timeStamp = new Date().getTime();
        creatingTime = timeStamp - timeOfCreating;
    }

    public String createMagicHash() {
        Random rand = new Random();

        while (!checkZeroes(getHash(), zerosNumber)) {
            magic = rand.nextInt(Integer.MAX_VALUE);
        }

        return getHash();
    }

    private static boolean checkZeroes(String s, int n) {
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) != '0') return false;
        }

        return true;
    }

    public String getHash() {
        return StringUtil.applySha256(id + Long.toString(timeOfCreating) + prevHash + magic);
    }

    public static boolean areConsequentBlocks(Block previousBlock, Block lastBlock, int zerosNumber) {
        return (lastBlock.prevHash.equals(previousBlock.hash) && checkZeroes(lastBlock.hash, zerosNumber));
    }
}

class StringUtil {
    /* Applies Sha256 to a string and returns a hash. */
    public static String applySha256(String input){
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            /* Applies sha256 to our input */
            byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte elem: hash) {
                String hex = Integer.toHexString(0xff & elem);
                if(hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        }
        catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}

问题是我已经在没有同步的情况下测试了此代码,结果仍然很好!我想到的第一个想法是,由于在将新块添加到我们的区块链之前先检查其真实性,所以我得到了这样的结果。但是我们必须注意,我们的块向量(BlockChain.chain字段)未标记为易失性,因此我们无法保证传递给check方法的最后一个区块链的值是最新的。这意味着可能会发生错误,但我不明白为什么它不会...

预先谢谢您,如果我对我的代码的描述不佳,请随时提问。