
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); | |
| } | |
| } |
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; | |
| } | |
| } |
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()); | |
| } | |
| } | |
| } |
| 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)); | |
| } | |
| } |
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())); | |
| } | |
| } |
Start realizado através da classe Run.
A imagem abaixo apresenta o output da aplicação.

Bons códigos! </>
Muito bom!
CurtirCurtir
Fascinante.Agora é tentar reproduzir…
CurtirCurtir
Top!!
CurtirCurtir