Java 使用ConcurrentHashMap实现并行性

Java 使用ConcurrentHashMap实现并行性,java,concurrency,parallel-processing,hashmap,Java,Concurrency,Parallel Processing,Hashmap,我有一个程序,试图理解线程并行性。这个程序处理抛硬币,计算正面和反面的数量(以及抛硬币的总数) 请参阅以下代码: import java.util.Random; import java.util.concurrent.ConcurrentHashMap; public class CoinFlip{ // main public static void main (String[] args) { if (args.length != 2){

我有一个程序,试图理解线程并行性。这个程序处理抛硬币,计算正面和反面的数量(以及抛硬币的总数)

请参阅以下代码:

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

public class CoinFlip{


    // main
    public static void main (String[] args) {
        if (args.length != 2){
            System.out.println("CoinFlip #threads #iterations");
            return;
        }

        // check if arguments are integers
        int numberOfThreads = 0;
        long iterations = 0;

        try{
            numberOfThreads = Integer.parseInt(args[0]);
            iterations = Long.parseLong(args[1]);
        }catch(NumberFormatException e){
            System.out.println("error: I asked for numbers mate.");
            System.out.println("error: " + e);
            System.exit(1);
        }

        // ------------------------------
        // set time field
        // ------------------------------


        // create a hashmap
        ConcurrentHashMap <String, Long> universalMap = new ConcurrentHashMap <String, Long> ();

        // store count for heads, tails and iterations
        universalMap.put("HEADS", new Long(0));
        universalMap.put("TAILS", new Long(0));
        universalMap.put("ITERATIONS", new Long(0));

        long startTime = System.currentTimeMillis();

        Thread[] doFlip = new Thread[numberOfThreads];

        for (int i = 0; i < numberOfThreads; i ++){
            doFlip[i] = new Thread( new DoFlip(iterations/numberOfThreads, universalMap));
            doFlip[i].start();
        }

        for (int i = 0; i < numberOfThreads; i++){
            try{
                doFlip[i].join();
            }catch(InterruptedException e){
                System.out.println(e);
            }
        }

        // log time taken to accomplish task
        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("Runtime:" + elapsedTime);

        // print the output to check if the values are legal
        // iterations = heads + tails = args[1]
        System.out.println(
            universalMap.get("HEADS") + " " +
            universalMap.get("TAILS") + " " +
            universalMap.get("ITERATIONS") + "."
        );

        return;
    }



    private static class DoFlip implements Runnable{

        // local counters for heads/tails/count
        long heads = 0, tails = 0, iterations = 0;
        Random randomHT = new Random();

        // constructor values -----------------------
        long times = 0; // number of iterations
        ConcurrentHashMap <String, Long> map; // pointer to hash map

        DoFlip(long times, ConcurrentHashMap <String, Long> map){
            this.times = times;
            this.map = map;
        }

        public void run(){
            while(this.times > 0){
                int r = randomHT.nextInt(2); // 0 and 1

                if (r == 1){
                    this.heads ++;
                }else{
                    this.tails ++;
                }
                // System.out.println("Happening...");
                this.iterations ++;
                this.times --;
            }

            updateStats();
        }


        public void updateStats(){
            // read from hashmap and get the existing values
            Long nHeads = (Long)this.map.get("HEADS");
            Long nTails = (Long)this.map.get("TAILS");
            Long nIterations = (Long)this.map.get("ITERATIONS");

            // update values
            nHeads = nHeads + this.heads;
            nTails = nTails + this.tails;
            nIterations = nIterations + this.iterations;

            // push updated values to hashmap
            this.map.put("HEADS", nHeads);
            this.map.put("TAILS", nTails);
            this.map.put("ITERATIONS", nIterations);

        }
    }
}
import java.util.Random;
导入java.util.concurrent.ConcurrentHashMap;
公共类硬币翻转{
//主要
公共静态void main(字符串[]args){
如果(参数长度!=2){
System.out.println(“CoinFlip#threads#iterations”);
回来
}
//检查参数是否为整数
int numberOfThreads=0;
长迭代=0;
试一试{
numberOfThreads=Integer.parseInt(args[0]);
迭代次数=Long.parseLong(args[1]);
}捕获(数字格式){
System.out.println(“错误:我要的是数字伴侣。”);
System.out.println(“错误:+e”);
系统出口(1);
}
// ------------------------------
//设置时间域
// ------------------------------
//创建哈希映射
ConcurrentHashMap universalMap=新的ConcurrentHashMap();
//存储头、尾和迭代的计数
通用表put(“头”,新长(0));
通用映射put(“TAILS”,新长(0));
universalMap.put(“迭代”,新长(0));
long startTime=System.currentTimeMillis();
线程[]doFlip=新线程[numberOfThreads];
for(int i=0;i0){
int r=randomHT.nextInt(2);//0和1
如果(r==1){
这个.heads++;
}否则{
这个.tails++;
}
//System.out.println(“正在发生…”);
这个.iterations++;
这一次--;
}
updateStats();
}
公共空间更新状态(){
//从hashmap读取并获取现有值
Long nHeads=(Long)this.map.get(“HEADS”);
Long nTails=(Long)this.map.get(“TAILS”);
Long nIterations=(Long)this.map.get(“迭代”);
//更新值
nHeads=nHeads+this.heads;
nTails=nTails+this.tails;
硝化=硝化+this.iterations;
//将更新的值推送到hashmap
这个.map.put(“HEADS”,nHeads);
这个.map.put(“TAILS”,nTails);
this.map.put(“迭代”,nIterations);
}
}
}
我使用ConcurrentHashMap来存储不同的计数。显然,当返回错误的值时


我编写了一个PERL脚本来检查头和尾的(总和)值(分别针对每个线程),这似乎是合适的。我无法理解为什么我从hashmap中获取不同的值。

您应该使用原子长度作为值,并且只创建一次原子长度,并将其递增,而不是get/put

 ConcurrentHashMap <String, AtomicLong> universalMap = new ConcurrentHashMap <String, AtomicLong> ();
 ...
 universalMap.put("HEADS", new AtomicLong(0));
 universalMap.put("TAILS", new AtomicLong(0));
 universalMap.put("ITERATIONS", new AtomicLong(0));
 ...
 public void updateStats(){
        // read from hashmap and get the existing values
        this.map.get("HEADS").getAndAdd(heads);
        this.map.get("TAILS").getAndAdd(tails);
        this.map.get("ITERATIONS").getAndAdd(iterations);
 }
现在,您的地图包含5个,而不是20个


基本上你的问题不是地图。您可以使用常规HashMap,因为您不需要修改它。当然,您必须使
map
字段
final

一个并发哈希映射为您提供了关于映射本身而不是其值的更改可见性的保证。在这种情况下,您可以从映射中检索一些值,将它们保留任意时间,然后再次尝试将它们存储到映射中。然而,在读操作和随后的写操作之间,地图上可能发生了任意数量的操作

例如,concurrent-in-concurrent散列映射只是保证,如果我将一个值放入映射中,我将能够在另一个线程中读取该值(也就是说,它将是可见的)

您需要做的是确保访问映射的所有线程在更新共享计数器时等待轮到它们。为此,您必须在AtomicInteger上使用类似“addAndGet”的原子操作:

this.map.get("HEADS").addAndGet(this.heads);
或者您需要手动同步读取和写入(最容易通过在映射本身上同步完成):


就我个人而言,我更喜欢尽可能利用SDK,因此我会使用原子数据类型。

一些事情。你真的不需要使用ConcurrentHashMap。ConcurrentHashMap仅在处理并发put/removes时有用。在这种情况下,地图是相当静态的,只要使用一个不可修改的地图来证明这一点

最后,如果您处理的是并发添加y
this.map.get("HEADS").addAndGet(this.heads);
synchronized(this.map) {
    Long currentHeads = this.map.get("HEADS");
    this.map.put("HEADS", Long.valueOf(currentHeads.longValue() + this.heads);
}
public class HeadsTails{
    private final Map<String, LongAdder> map;
    public HeadsTails(){
       Map<String,LongAdder> local = new HashMap<String,LongAdder>();
       local.put("HEADS", new LongAdder());
       local.put("TAILS", new LongAdder());
       local.put("ITERATIONS", new LongAdder());
       map = Collections.unmodifiableMap(local);
    }
    public void count(){
        map.get("HEADS").increment();
        map.get("TAILS").increment();
    }
    public void print(){
        System.out.println(map.get("HEADS").sum());
         /// etc...
    }
}
public class HeadsTails{
    private final LongAdder heads = new LongAdder();
    private final LongAdder tails = new LongAdder();
    private final LongAdder iterations = new LongAdder();
    private final Map<String, LongAdder> map;
    public void count(){
        heads.increment();
        tails.increment();
    }
    public void print(){
        System.out.println(iterations.sum());
    }
}