Explorando a simultaneidade em Java – de threads à simultaneidade estruturada | Geekware

Explorando a simultaneidade em Java – de threads à simultaneidade estruturada | Geekware


Java 25

A jornada do Java na simultaneidade tem sido longa e fascinante — evoluindo de threads gerenciados manualmente para threads virtuais leves e simultaneidade estruturada na era moderna. Nesta postagem, exploraremos diversas abordagens para executar tarefas simultaneamente, mostrando como cada etapa melhora a simplicidade, a legibilidade e a escalabilidade.

Você pode executar esses exemplos com Java 25 diretamente de .java arquivos de origem:

(!NOTE) Para o escopo desta postagem, não cobriremos os bons e velhos Threads (threads gerenciados pelo sistema operacional), mas começaremos diretamente usando Virtual Threads (threads gerenciados por JVM).

1. Começando com o básico – The Raw Thread

Aqui, só queremos executar uma tarefa simples simultaneamente usando um Thread (um virtual um). É o alicerce mais fundamental para simultaneidade em Java.

void main() throws InterruptedException {
    Thread worker = Thread.startVirtualThread(() -> {
        IO.println("Running in a separate thread: " + Thread.currentThread().getName());
    });

    worker.join();
    IO.println("Main thread finished.");
}

O que está acontecendo:

  • Criamos e iniciamos um novo tópico virtual para executar uma tarefa.
  • join() garante que o thread principal aguarde a conclusão.

2. Conjuntos de threads com ExecutorService

Neste exemplo, queremos execute várias tarefas com eficiência usando um conjunto de threads de trabalho. Isso evita a criação de muitos threads e nos permite gerenciar melhor os recursos.

import java.util.concurrent.*;
import java.util.stream.*;

void main() throws Exception {
    try (var executor = Executors.newFixedThreadPool(4)) {
        var futures = IntStream.range(0, 5)
            .mapToObj(i -> executor.submit(() -> {
                Thread.sleep(200);
                return "Task " + i + " done by " + Thread.currentThread().getName();
            }))
            .toList();

        for (var f : futures) IO.println(f.get());
    }
}

O que está acontecendo:

  • Enviamos 5 tarefas para um conjunto de 4 threads.
  • Cada tarefa fica suspensa por um curto período e depois retorna um resultado.
  • O thread principal recupera todos os resultados via Future.get().

3. Pipelines assíncronos com CompletableFuture

Agora, vamos operações assíncronas em cadeia para simular a busca e o processamento de dados de maneira sem bloqueio.

import java.util.concurrent.*;

void main() {
    try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
        CompletableFuture.supplyAsync(() -> {
            IO.println("Fetching data...");
            sleep(300);
            return "Data";
        }, pool)
        .thenApply(data -> {
            IO.println("Processing " + data);
            return data.length();
        })
        .thenAccept(len -> IO.println("Result length: " + len))
        .join();
    }
}

void sleep(long ms) {
    try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
}

O que está acontecendo:

  • CompletableFuture executa cada etapa de forma assíncrona.
  • Os resultados fluem de um estágio para o outro sem bloqueios.
  • Esse padrão é poderoso para operações com uso pesado de IO.

4. Simultaneidade Estruturada – Subtarefas de Coordenação Limpa

Neste exemplo final, queremos iniciar duas subtarefas simultaneamenteespere que ambos terminem (ou cancele o outro se um falhar) e depois combine seus resultados. A simultaneidade estruturada fornece uma maneira mais segura e clara de lidar com diversas tarefas simultâneas como parte de uma única operação lógica.

import java.time.Instant;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

void main() throws Exception {
    try (var scope = StructuredTaskScope.open()) {
        Subtask<String> users = scope.fork(() -> loadData("users"));
        Subtask<String> orders = scope.fork(() -> loadData("orders"));

        // Wait for both subtasks to complete or for one to fail
        scope.join();

        // If join succeeded, both tasks completed successfully
        IO.println("Results: %s | %s"
                  .formatted(users.get(), orders.get()));
    }
}

String loadData(String type) throws InterruptedException {
    IO.println("Loading %s...".formatted(type));
    Thread.sleep(300);
    return "Loaded %s at %s".formatted(type, Instant.now());
}

(!IMPORTANTE) ⚠️ Requer --enable-preview sinalizador em Java 25.

O que está acontecendo:

  • StructuredTaskScope.open() cria um escopo para subtarefas simultâneas.
  • Cada subtarefa é digitada como Subtask para maior clareza, usando a importação.
  • fork(...) agenda uma subtarefa dentro do escopo.
  • scope.join() espera que todas as tarefas terminem ou cancela outras se alguma falhar.
  • Os resultados são recuperados com segurança usando get().

Há mais vantagens em usar a simultaneidade de estrutura, como cancelamento de escopo, que permite lidar com tarefas simultâneas complexas.

Usando Threads Virtuais no Spring Boot

Bota Primavera

Com os threads virtuais e a simultaneidade estruturada do Java 25, vimos como gerenciar com eficiência tarefas simultâneas em aplicativos independentes. Esses mesmos princípios podem ser aplicados em estruturas modernas como Spring Boot, onde threads de servidor que lidam com solicitações HTTP também podem aproveitar threads virtuais. Isso permite o processamento de solicitações escalonável e sem bloqueio, ao mesmo tempo que mantém o código legível e de fácil manutenção.

Spring Boot 3.2+ suporta o uso tópicos virtuais para lidar com solicitações HTTP e outras tarefas, aproveitando os recursos do Java 19+ Project Loom. Isso pode melhorar muito a escalabilidade para cargas de trabalho vinculadas a E/S.

Configurando Threads Virtuais

Habilitar threads virtuais no Spring é tão simples quanto incluir uma classe de configuração como a seguinte:

@EnableAsync
@Configuration
public class ThreadConfig {
    @Bean
    public AsyncTaskExecutor applicationTaskExecutor() {
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }

    @Bean
    public TomcatProtocolHandlerCustomizer protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

Agora, se você definir um novo controlador como o seguinte:

@RestController
@RequestMapping("/thread")
public class ThreadController {
    @GetMapping("/name")
    public String getThreadName() {
        return Thread.currentThread().toString();
    }
}

e consulte:

$ curl -s http://localhost:8080/thread/name
$ VirtualThread(#171)/runnable@ForkJoinPool-1-worker-4

você poderá validar se de fato a solicitação foi tratada por uma instância de um thread virtual.

Você pode encontrar um tutorial mais detalhado sobre Spring e Virtual Threads aqui: https://www.baeldung.com/spring-6-virtual-threads

Lições aprendidas

Cada etapa melhora a experiência do desenvolvedor, mas também explica quais problemas a solução mais avançada resolve:

  • Tópicos: Simples, mas sujeito a erros para múltiplas tarefas.
  • Executores: Gerencie recursos com eficiência.
  • Futuro completável: Pipelines expressos de trabalho assíncrono.
  • Simultaneidade Estruturada: Fornece escopo claro, cancelamento automático e tratamento simples de erros.

Conclusão

Java agora oferece uma rica caixa de ferramentas para programação simultânea:

  • Tópicos são melhores para tarefas simples e únicas em segundo plano.
  • Executores são ideais para controlar o número de threads ativos.
  • CompletávelFuturo brilha para compor tarefas assíncronas dependentes.
  • Simultaneidade Estruturada traz ordem e segurança a fluxos de trabalho simultâneos complexos, alinhando-se com a forma como os humanos pensam sobre tarefas e subtarefas.

Cada abordagem se baseia na anterior – culminando em um modelo onde a simultaneidade não é apenas eficiente, mas também estruturado, previsível e legível. O Java moderno nunca fez com que a simultaneidade parecesse tão acessível.



Source link

Postagens Similares

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *