Multithread – Java, executors e leitura de arquivos

Demonstração da utilização de Executors da API java.util.concurrent.

Java

Aproveitando o clima de copa do mundo, a aplicação desenvolvida realiza a leitura de arquivos com informações dos jogadores dos grupos F, G e H.

Essa implementação utiliza Executors da API java.util.concurrent.

Exemplo do arquivo:

Vamos começar criando o modelo para trabalhar com os dados.

package blog.matheuscarvalho.multithreadfilereader.model;
import java.util.Objects;
public class Player {
private String name;
private String position;
private String country;
public Player(String name, String position, String country) {
this.name = name;
this.position = position;
this.country = country;
}
public String getName() {
return name;
}
public String getCountry() {
return country;
}
public String getPosition() {
return position;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Nome do jogador: ").append(this.name).append(" | Posição: ").append(position).append(" | País: ")
.append(country);
return sb.toString();
}
@Override
public int hashCode() {
return Objects.hash(country, name, position);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Player other = (Player) obj;
return Objects.equals(country, other.country) && Objects.equals(name, other.name)
&& Objects.equals(position, other.position);
}
}
view raw Player.java hosted with ❤ by GitHub

Abaixo uma enum mapeando o diretório e o delimitador das informações.

package blog.matheuscarvalho.multithreadfilereader.enums;
public enum FileEnum {
PLAYER_SPLITTER("\\s{4}"), PLAYER_DIRECTORY("F:\\Development\\Util\\Files\\");
private String value;
private FileEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
view raw FileEnum.java hosted with ❤ by GitHub

Na classe FileUtil o método listFiles retorna a lista de arquivos do diretório informado.

package blog.matheuscarvalho.multithreadfilereader.file.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileUtil {
public static Set<String> listFiles(String dir) throws IOException {
try (Stream<Path> stream = Files.list(Paths.get(dir)).parallel()) {
return stream.filter(file -> !Files.isDirectory(file)).map(Path::toString).collect(Collectors.toSet());
}
}
}
view raw FileUtil.java hosted with ❤ by GitHub
package blog.matheuscarvalho.multithreadfilereader.file.impl;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import blog.matheuscarvalho.multithreadfilereader.enums.FileEnum;
import blog.matheuscarvalho.multithreadfilereader.file.FileReader;
import blog.matheuscarvalho.multithreadfilereader.model.Player;
public class PlayerReader implements FileReader<Player> {
@Override
public Set<Player> read(Path path) throws IOException {
Set<Player> players = Collections.synchronizedSet(new HashSet<>());
try (Stream<String> lines = Files.lines(path)) {
lines.forEach(s -> {
String[] splited = s.split(FileEnum.PLAYER_SPLITTER.getValue());
if (splited.length > 0 && splited.length == 3) {
players.add(new Player(splited[0], splited[1], splited[2]));
}
});
}
return players;
}
}

A classe PlayerReader implementa a interface FileReader, faz o parse de cada linha, criando os objetos que representam cada jogador.

A partir daqui à aplicação torna-se multithread através das classes:

PlayerTask, BaseExecutor e PlayerExecutor.

package blog.matheuscarvalho.multithreadfilereader.thread;
import java.nio.file.Paths;
import java.util.Set;
import java.util.concurrent.Callable;
import blog.matheuscarvalho.multithreadfilereader.file.FileReader;
import blog.matheuscarvalho.multithreadfilereader.file.impl.PlayerReader;
import blog.matheuscarvalho.multithreadfilereader.model.Player;
public class PlayerTask implements Callable<Set<Player>> {
private FileReader<Player> reader;
private String path;
PlayerTask(String path) {
this.reader = new PlayerReader();
this.path = path;
}
@Override
public Set<Player> call() throws Exception {
System.out.println("Thread ID");
System.out.println(Thread.currentThread().getId());
return reader.read(Paths.get(path));
}
}
view raw PlayerTask.java hosted with ❤ by GitHub

A classe PlayerTask implementa a interface Callable.

O Set de Players será retornado através do método call.

package blog.matheuscarvalho.multithreadfilereader.thread;
import java.io.IOException;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import blog.matheuscarvalho.multithreadfilereader.enums.FileEnum;
import blog.matheuscarvalho.multithreadfilereader.file.util.FileUtil;
import blog.matheuscarvalho.multithreadfilereader.model.Player;
public class PlayerExecutor extends BaseExecutor<String, Set<Player>> {
public PlayerExecutor() {
super(Executors.newCachedThreadPool());
}
@Override
public Map<String, Set<Player>> run() throws IOException, Exception {
Map<String, Set<Player>> mapFilesPlayers = Collections.synchronizedMap(new Hashtable<>());
Set<String> playersFiles = FileUtil.listFiles(FileEnum.PLAYER_DIRECTORY.getValue());
Map<String, Future<Set<Player>>> futures = Collections.synchronizedMap(new Hashtable<>());
playersFiles.stream().forEach(f -> {
Future<Set<Player>> future = executor.submit(new PlayerTask(f));
futures.put(f, future);
});
futures.forEach((key, value) -> {
try {
mapFilesPlayers.put(key, value.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
executor.shutdown();
return mapFilesPlayers;
}
}

Em PlayerExecutor na linha 27, a lista com os arquivos do diretório parametrizado é retornada.

Podemos substituir playersFiles.stream() por playersFiles.parallelStream() conforme o aumento do volume de dados.

Dentro do forEach uma Future é criada representando uma execução assíncrona de uma Task para cada arquivo.  executor.submit(new PlayerTask(f)).

Além do submit, poderíamos utilizar o invokeAll ou invokeAny ambos recebem uma collection de tasks, porém o invokeAny retorna somente o resultado de uma execução.

package blog.matheuscarvalho.multithreadfilereader.thread;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ExecutorService;
public abstract class BaseExecutor<S, T> {
protected ExecutorService executor;
public BaseExecutor(ExecutorService executor) {
this.executor = executor;
}
public abstract Map<S, T> run() throws IOException, Exception;
public ExecutorService getExecutor() {
return executor;
}
public void setExecutor(ExecutorService executor) {
this.executor = executor;
}
}

A classe abstrata BaseExecutor possui a propriedade executor que pode ser configurada para cada implementação.

Em PlayerExecutor Executors.newCachedThreadPool() um pool onde as threads são criadas conforme a necessidade/recurso, também reutilizando as threads que foram criadas anteriormente.

Outra opção para esse contexto seria o  newFixedThreadPool(int nThreads) que utiliza um número fixo de threads.

package blog.matheuscarvalho.multithreadfilereader.start;
import java.util.Map;
import java.util.Set;
import blog.matheuscarvalho.multithreadfilereader.model.Player;
import blog.matheuscarvalho.multithreadfilereader.thread.PlayerExecutor;
public class Run {
public static void main(String[] args) throws Exception {
Map<String, Set<Player>> mapFilePLayers = new PlayerExecutor().run();
mapFilePLayers.entrySet().stream().forEach(e -> System.out.println(e.getKey() + " : " + e.getValue()));
}
}
view raw Run.java hosted with ❤ by GitHub

Start realizado através da classe Run.

A imagem abaixo apresenta o output da aplicação.

Link do repositório!

Bons códigos! </>

3 comentários em “Multithread – Java, executors e leitura de arquivos”

Deixar mensagem para Kayo Renan Cancelar resposta