Алгоритмы Java-коллекций в многопоточном режиме

1

У меня есть задача выполнить некоторую связанную с коллекцией логику в параллельных потоках и сравнить ее с режимом одиночного потока. Из этого вопроса многопоточность для чтения файла в Java я заметил, что чтение файлов не является задачей многопоточности, поэтому я решил сосредоточиться на дальнейшей логике. Логика такова:

  public List<?> taskExecution(File file, boolean parallel) {
    List<Entry<String, Integer>> entryList = new ArrayList<>();
    try {
        if (parallel) {
            entryList = taskExecutionInParallel(file);
        } else {
            // put in the map the words and their occurrence 
            Map<String, Integer> wordsFrequency = new HashMap<>();
            for(String word : this.readWordsFromText(file, parallel)) {
                if (wordsFrequency.containsKey(word)) {
                    wordsFrequency.put(word, wordsFrequency.get(word).intValue() + 1);
                } else {
                    wordsFrequency.put(word, 1);
                }
            }

            // create the list of Map.Entry objects
            entryList.addAll(wordsFrequency.entrySet());

            // sort the entries by the value descending
            Collections.sort(entryList, new Comparator<Entry<String, Integer>>(){

                @Override
                public int compare(Entry<String, Integer> o1,
                        Entry<String, Integer> o2) {
                    return o2.getValue().compareTo(o1.getValue());
                }

            });

            // identify the top index
            int topIndex = entryList.size() > 1 ? 2 : entryList.size() > 0 ? 1 : 0;

            // truncate the list
            entryList = entryList.subList(0, topIndex);

            // sort the result list by the words descending
            Collections.sort(entryList, new Comparator<Entry<String, Integer>>(){

                @Override
                public int compare(Entry<String, Integer> o1,
                        Entry<String, Integer> o2) {
                    return o2.getKey().compareTo(o1.getKey());
                }

            });
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return entryList;
}

Я пытаюсь выполнить преобразование из списка исходных слов на карту с частотами слов с рамкой Fork/Join:

class ForkJoinFrequencyReader extends RecursiveAction {

    static final int SEQUENTIAL_THRESHOLD = 1000;

    private static final long serialVersionUID = -7784403215745552735L;
    private Map<String, Integer> wordsFrequency;
    private final int start;
    private final int end;
    private final List<String> words;

    public ForkJoinFrequencyReader(List<String> words, Map<String, Integer> wordsFrequency) {
        this(words, 0, words.size(), wordsFrequency);
    }

    private ForkJoinFrequencyReader(List<String> words, int start, int end, Map<String, Integer> wordsFrequency) {
        this.words = words;
        this.start = start;
        this.end = end;
        this.wordsFrequency = wordsFrequency;
    }

    private synchronized void putInMap() {
        for(int i = start; i < end; i++) {
            String word = words.get(i);
            if (wordsFrequency.containsKey(word)) {
                wordsFrequency.put(word, wordsFrequency.get(word).intValue() + 1);
            } else {
                wordsFrequency.put(word, 1);
            }
        }
    }

    @Override
    protected void compute() {
        if (end - start < SEQUENTIAL_THRESHOLD) {
            putInMap();
        } else {
            int mid = (start + end) >>> 1;
            ForkJoinFrequencyReader left = new ForkJoinFrequencyReader(words, start, mid, wordsFrequency);
            ForkJoinFrequencyReader right = new ForkJoinFrequencyReader(words, mid, end, wordsFrequency);
            left.fork();
            right.fork();
            left.join();
            right.join();
        }
    }

}

private List<Entry<String, Integer>> taskExecutionInParallel(File file) throws IOException {
    List<Entry<String, Integer>> entryList = new CopyOnWriteArrayList<>();

    ForkJoinPool pool = new ForkJoinPool();
    Map<String, Integer> wordsFrequency = new ConcurrentHashMap<>();
    pool.invoke(new ForkJoinFrequencyReader(Collections.synchronizedList(this.readWordsFromText(file, true)), wordsFrequency));

 //****** .... the same single-thread code yet
}

Но приведенная карта имеет разные значения после каждого исполнения. Может ли кто-нибудь указать мне, где это узкое место, или предложить некоторые другие решения для внедрения параллелизма с использованием стандартного JDK до версии 7?

Теги:
multithreading
collections
concurrency
fork-join

3 ответа

0
Лучший ответ

Ваш putInMap синхронизируется на конкретном экземпляре ForkJoinFrequencyReader. В то же время вы создаете разные экземпляры ForkJoinFrequencyReader в методе вычисления. Таким образом, ваша синхронизация просто не работает, потому что каждый из них связан с собственным экземпляром. Чтобы проверить это, просто замените putInMap на

private void putInMap() {
    synchronized (wordsFrequency) {

Прочтите это, например: http://www.cs.umd.edu/class/fall2013/cmsc433/examples/wordcount/WordCountParallel.java

  • 0
    Спасибо, ваше замечание помогло мне. Это работает сейчас. Я также думаю, что рассмотрение ссылки BlockingQueue может быть решением для этой задачи.
1

Я реализовал также шаблон "Продюсер-потребитель" для блока частоты слов:

private Map<String, Integer> frequencyCounterInParallel(File file) throws InterruptedException {
    Map<String, Integer> wordsFrequency = Collections.synchronizedMap(new LinkedHashMap<>());
    BlockingQueue<String> queue = new ArrayBlockingQueue<>(1024);
    Thread producer = new Thread(new Producer(queue, file));
    Thread consumer = new Thread(new Consumer(queue, wordsFrequency));
    producer.start();
    consumer.start();
    producer.join();
    consumer.join();
    return wordsFrequency;
}

class Producer implements Runnable {

    private BlockingQueue<String> queue;
    private File file;

    public Producer(BlockingQueue<String> queue, File file) {
        this.file = file;
        this.queue = queue;
    }

    @Override
    public void run() { 
        try(BufferedReader bufferReader = Files.newBufferedReader(file.toPath())) {
            String line = null;
            while ((line = bufferReader.readLine()) != null){
                String[] lineWords = line.split(CommonConstants.SPLIT_TEXT_REGEX); 
                for(String word : lineWords) {
                    if (word.length() > 0) {
                        queue.put(word.toLowerCase());
                    }
                }
            }
            queue.put(STOP_THREAD);
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }
}

class Consumer implements Runnable {


    private BlockingQueue<String> queue;
    private Map<String, Integer> wordsFrequency;

    public Consumer(BlockingQueue<String> queue, Map<String, Integer> wordsFrequency) {
        this.queue = queue;
        this.wordsFrequency = wordsFrequency;
    }

    @Override
    public void run() {
        try {
            String word = null;
            while(!((word = queue.take()).equals(STOP_THREAD))) {
                if (wordsFrequency.containsKey(word)) {
                    wordsFrequency.put(word, wordsFrequency.get(word).intValue() + 1);
                } else {
                    wordsFrequency.put(word, 1);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }   
    }

}
0

Вероятно, вы должны использовать возможности параллельного вывода потоков Java 8:

Path path = FileSystems.getDefault().getPath(...);
Stream<String> words = Files.lines(path);
Map<String, Long> wordsFrequency = words.parallel()
    .collect(Collectors.groupingBy(UnaryOperator.identity(),
                                   Collectors.counting()));
  • 0
    спасибо, но, как я упоминал ранее, мне нужно было решение в Java 7, решение с использованием Java 8 Streams API - это еще одна задача, и я уже сделал это. Это выглядит как:
  • 0
    list = Collections.synchronizedList(this.readWordsFromText(file, parallel)).parallelStream().collect(groupingByConcurrent(e -> e, counting())).entrySet().parallelStream().sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())).limit(2).sorted((e1, e2) -> e2.getKey().compareTo(e1.getKey())).collect(toList());
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню