Is my code thread-unsafe?

血红的双手。 提交于 2019-12-05 00:37:43

It seems error in method acceptVote(int index)

Try to rewrite it use method ConcurrentHashMap.putIfAbsent and AtomicInteger:

private final ConcurrentHashMap<String, AtomicInteger> results = new ConcurrentHashMap<>();

public void acceptVote(int index) {
    AtomicInteger currentResult = results.get(candidates.get(index));
    if (currentResult == null) {
        currentResult = results.putIfAbsent(candidates.get(index), new AtomicInteger(1));
        if (currentResult != null) {
            currentResult.getAndIncrement();
        }
    } else {
        currentResult.getAndIncrement();
    }
}

something like this...

I tried several times with a synchronized list for candidates, at the results are consistent and as expected:

class Main {
    public static final int VOTE_COUNT = 10000;

    public static void main(String[] args) {
        List<String> candidates = Collections.synchronizedList(new ArrayList<>());
      .....
}

Not the best solution though.

A better solution would be to synchronize just the access to accepted voters. This can be done:

class ElectoralCommission {
  //....

    public synchronized void acceptVote(int index) {
        Integer currentResult = results.get(candidates.get(index));
        results.put(candidates.get(index), currentResult == null ? 1 : currentResult + 1);
}

but this is not the best way, as it will add lock the whole object. A better approach would be using a lock:

    class ElectoralCommission {
  //....
        private ReentrantLock lock = new ReentrantLock(); 

        public void acceptVote(int index) {
            try {
               lock.lock();
               Integer currentResult = results.get(candidates.get(index));
               results.put(candidates.get(index), currentResult == null ? 1 : currentResult + 1);
           }finally {
               lock.unlock();
           }
        }

Output 1:

result:
key=candidate_6, value=3315
key=candidate_7, value=3315
key=candidate_10, value=3370
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
candidate candidate_10 won!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Building a bit on Sergey's answer, if you seed the results collection in the ElectoralCommission constructor:

ElectoralCommission(List<String> candidates) {
    this.candidates = candidates;
    for (String candidate : candidates) {
        results.put(candidate, new AtomicInteger());
    }
}

Then acceptVote becomes:

public void acceptVote(int index) {
    results.get(candidates.get(index)).getAndIncrement();
}

The following variants works for atomics:

class ElectoralCommission {
    public volatile boolean hasWinner;
    public volatile String winner;
    private List<String> candidates;
    private Map<String, AtomicInteger> results = new ConcurrentHashMap<>();

    private Map<String, AtomicInteger> initializeMap() {
        candidates.stream().forEach(i -> results.put(i, new AtomicInteger()));
        return results;
    }

    ElectoralCommission(List<String> candidates) {
        this.candidates = candidates;
        results = initializeMap();
    }

    public void acceptVote(int index) {
        results.get(candidates.get(index)).incrementAndGet();
    }

    public void clearResults() {
        results.clear();
        initializeMap();
    }

    public List<String> getCandidates() {
        return candidates;
    }

    public String getWinner() {
        return winner;
    }

    public void printResults() {
        System.out.println("result:");
        for (Map.Entry<String, AtomicInteger> entry : results.entrySet()) {
            System.out.println("key=" + entry.getKey() + ", value=" + entry.getValue());
        }
    }

    public void removeWeakCandidates() {
        int minVoteValue = Collections.min(results.values().stream().map(i->i.intValue()).collect(Collectors.toList()));
        int maxVoteValue = Collections.max(results.values().stream().map(i->i.intValue()).collect(Collectors.toList()));
        if (maxVoteValue == minVoteValue) {
            System.out.println("all candidates got same votes count");
        } else {
            List<String> loosers = results.entrySet().stream().filter(i -> i.getValue().intValue() == minVoteValue).map(i -> i.getKey()).collect(Collectors.toList());
            candidates.removeAll(loosers);
            if (candidates.size() == 1) {
                hasWinner = true;
                winner = candidates.get(0);
            }
        }
        clearResults();
    }
}

class Voter implements Runnable {
    ElectoralCommission electoralCommission;
    CyclicBarrier cyclicBarrier;

    Voter(CyclicBarrier cyclicBarrier, ElectoralCommission electoralCommission) {
        this.cyclicBarrier = cyclicBarrier;
        this.electoralCommission = electoralCommission;
    }

    @Override
    public void run() {
        while (!electoralCommission.hasWinner) {
            List<String> candidates = electoralCommission.getCandidates();
            int voteIndex = new Random().nextInt(candidates.size());
            electoralCommission.acceptVote(voteIndex);
            try {
                cyclicBarrier.await(); //wait while all voters vote
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

class Main {
    public static final int VOTE_COUNT = 10000;

    public static void main(String[] args) {
        List<String> candidates = new ArrayList<>();
        candidates.add("candidate_1");
        candidates.add("candidate_2");
        candidates.add("candidate_3");
        candidates.add("candidate_4");
        candidates.add("candidate_5");
        candidates.add("candidate_6");
        candidates.add("candidate_7");
        candidates.add("candidate_8");
        candidates.add("candidate_9");
        candidates.add("candidate_10");
        ElectoralCommission electoralCommission = new ElectoralCommission(candidates);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(VOTE_COUNT, new Summarizer(electoralCommission));
        for (int i = 0; i < VOTE_COUNT; i++) {
            new Thread(new Voter(cyclicBarrier, electoralCommission)).start();
        }

    }
}

class Summarizer implements Runnable {
    static int roundNumber = 1;
    ElectoralCommission electoralCommission;

    Summarizer(ElectoralCommission electoralCommission) {
        this.electoralCommission = electoralCommission;
    }

    @Override
    public void run() {
        System.out.println("Round " + roundNumber++ );
        electoralCommission.printResults();
        electoralCommission.removeWeakCandidates();
        if (electoralCommission.hasWinner) {
            String winnerName = electoralCommission.getWinner();
            System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            System.out.println("candidate " + winnerName + " won!");
            System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!