Implementando Paralelismo com Virtual Threads no Java 21
Tags: java, braziliandevs, virtualthread
Published: 2023-10-30 03:30:52

Neste exemplo, veremos como implementar o paralelismo usando Java 21. Anteriormente, para alcançar paralelismo, utilizávamos e ainda usamos o `CompletableFeature`, que funciona muito bem, mas ainda é um pouco verboso.

Para começar, criaremos dois métodos que retornam uma String. Dentro de cada método, faremos a Thread esperar por alguns segundos para simbolizar uma operação de I/O.

```java
private static String executeTask1() throws InterruptedException {
	logger.info("task 1");
  Thread.sleep(1000);
	return "task1";
}
```

O primeiro método espera 1 segundo antes de retornar a String, e o segundo método é semelhante, também aguardando 1 segundo.

```java
private static String executeTask2() throws InterruptedException {
	logger.info("task 2");
  Thread.sleep(1000);
	return "task2";
}
```

Agora, criaremos um método que chamará esses dois métodos de forma paralela.

```java
private static String startVirtualThreads() throws InterruptedException, ExecutionException {

	try(var executor = Executors.newVirtualThreadPerTaskExecutor()){
		var task1 = executor.submit(AppThreadPerTask::executeTask1);
    var task2 = executor.submit(AppThreadPerTask::executeTask2);
    return task1.get() + task2.get();
	}
}
```

Ao analisar o código acima, observaremos a criação do `executor` usando o utilitário `Executors`. No Java 21, foi adicionado o método que cria um `ExecutorService` usando `VirtualThreads`: o `newVirtualThreadPerTaskExecutor()`. Com esse método, podemos criar uma Virtual Thread para cada tarefa. Observamos a separação das tarefas nas linhas 4 e 5 do exemplo.

O método `submit` recebe `Callable<V>`, que é uma interface funcional do Java, e retorna um `Future`. O `Future` entrega o retorno do método quando este é finalizado com sucesso, funcionando de forma similar às `Promises` do JavaScript [link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

É importante observar que utilizamos o método `.get()` para recuperar os valores dos métodos que estão sendo processados de forma paralela. Se algum dos métodos retornar uma exceção, podemos capturá-la facilmente no método principal.

Agora podemos chamar o método principal, que fará tudo funcionar. É recomendável monitorar os tempos de execução para visualização os tempos.

```java
public static void main(String[] args) throws InterruptedException, ExecutionException {

        Instant start = Instant.now();
        logger.info("init");
        String tasksConcatenated = startVirtualThreads();
        logger.info(tasksConcatenated);

        Instant end = Instant.now();
        Duration timeElapsed = Duration.between(start, end);
        logger.info("Time taken: "+ timeElapsed.toMillis() +" milliseconds");
    }

```

Após a execução, teremos um log mais ou menos como o seguinte:

```console
2023-10-29 23:03:12 INFO  AppThreadPerTask:26 - trace=121212 - init
2023-10-29 23:03:12 INFO  AppThreadPerTask:64 - trace= - task 1
2023-10-29 23:03:12 INFO  AppThreadPerTask:58 - trace= - task 2
2023-10-29 23:03:17 INFO  AppThreadPerTask:29 - trace=121212 - task1task2
2023-10-29 23:03:17 INFO  AppThreadPerTask:33 - trace=121212 - Time taken: 5018 milliseconds
```

Observamos nos logs que os métodos são registrados imediatamente, ou seja, estão sendo executados em paralelo. Isso significa que o método mais lento determinará o tempo de execução do método principal.

Repositório com código de exemplo: [Repositório](https://github.com/cassunde/java/blob/main/src/main/java/br/com/cassunde/AppThreadPerTask.java)

Você já utilizou alguma dessas funcionalidades em produção? Se sim, o que achou?

{% embed https://dev.to/cassunde/reduzindo-acoplamento-2ofm %}