0

スレッドの並列処理を理解しようとしているプログラムがあります。このプログラムは、コイントスを処理し、表と裏の数 (およびコイントスの合計数) をカウントします。

次のコードを参照してください。

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);

        }
    }
}

さまざまなカウントを格納するために ConcurrentHashMap を使用しています。どうやら、間違った値を返すとき。

ヘッドとテールの (合計) 値を (スレッドごとに個別に) チェックするための Perl スクリプトを作成しましたが、適切なようです。ハッシュマップから異なる値を取得する理由がわかりません。

4

3 に答える 3

4

同時ハッシュ マップは、その値ではなく、マップ自体に関する変更の可視性に関して保証を提供します。この場合、マップからいくつかの値を取得し、それらを任意の時間保持してから、再度マップに格納しようとします。ただし、読み取りとそれに続く書き込みの間に、マップ上で任意の数の操作が発生した可能性があります

同時ハッシュ マップの同時実行は、たとえば、値をマップに入れると、別のスレッドでその値を実際に読み取ることができる (つまり、表示される) ことを保証するだけです。

あなたがする必要があるのは、マップにアクセスするすべてのスレッドが、いわば、共有カウンターを更新するときに順番を待つようにすることです。これを行うには、AtomicInteger で「addAndGet」のようなアトミック操作を使用する必要があります。

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);
}

個人的には、できる限り SDK を利用することを好み、Atomic データ型を使用します。

于 2013-03-01T23:17:06.037 に答える
2

いくつかのこと。ConcurrentHashMap を使用する必要が本当にないもの。ConcurrentHashMap は、同時配置/削除を処理する場合にのみ役立ちます。この場合、キーがこれを証明するために単に UnmodifiableMap を使用する限り、マップはかなり静的です。

最後に、同時追加を扱っている場合は、LongAdderの使用を検討する必要があります。最後までカウントを気にする必要がない多くの並列追加が発生すると、はるかにうまくスケーリングされます。

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());
    }
}
于 2013-03-02T01:29:56.253 に答える
2

AtomicLongs を値として使用し、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);
 }

ロングは不変です。

例:

Thread 1: get 0
Thread 2: get 0
Thread 2: put 10
Thread 3: get 10
Thread 3: put 15
Thread 1: put 5

マップに 20 ではなく 5 が含まれるようになりました

基本的にあなたの問題はマップではありません。変更しないため、通常の HashMap を使用できます。もちろん、mapフィールドを作成する必要がありますfinal

于 2013-03-01T23:06:59.640 に答える