У меня есть задача выполнить некоторую связанную с коллекцией логику в параллельных потоках и сравнить ее с режимом одиночного потока. Из этого вопроса многопоточность для чтения файла в 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?
Ваш putInMap синхронизируется на конкретном экземпляре ForkJoinFrequencyReader. В то же время вы создаете разные экземпляры ForkJoinFrequencyReader в методе вычисления. Таким образом, ваша синхронизация просто не работает, потому что каждый из них связан с собственным экземпляром. Чтобы проверить это, просто замените putInMap на
private void putInMap() {
synchronized (wordsFrequency) {
Прочтите это, например: http://www.cs.umd.edu/class/fall2013/cmsc433/examples/wordcount/WordCountParallel.java
Я реализовал также шаблон "Продюсер-потребитель" для блока частоты слов:
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();
}
}
}
Вероятно, вы должны использовать возможности параллельного вывода потоков 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()));
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());