Threads em Java – Aprendendo Programação Paralela

Compartilhar:

As threads desempenham um papel fundamental na programação concorrente em Java. Elas permitem que os programas executem várias tarefas simultaneamente, melhorando a eficiência e a responsividade das aplicações.

Hoje em nosso portal, você vai conhecer em detalhes o conceito de threads em Java, suas características, como criá-las e gerenciá-las, além de boas práticas para o desenvolvimento de aplicações robustas e eficientes.

O que são Threads em Java?

Em Java, uma thread é uma unidade de execução, permitindo que um programa execute várias tarefas simultaneamente. Uma thread representa um único fluxo de controle dentro de um programa. Elas são úteis para executar tarefas independentes em paralelo, melhorando a eficiência e a responsividade de um aplicativo.

threads em java

Características Principais:

As threads permitem a execução simultânea de múltiplas tarefas dentro de um programa Java. Isso é especialmente útil em aplicativos que exigem operações concorrentes, como aplicativos de rede, servidores web e aplicativos de interface gráfica do usuário (GUI).

As threads em Java são relativamente leves em termos de recursos do sistema. Elas consomem menos memória e têm um tempo de criação mais rápido em comparação com os processos tradicionais.

Cada thread possui seu próprio contexto de execução, incluindo a pilha de chamadas (stack). Isso significa que as variáveis locais e o estado da thread são isolados um do outro, evitando conflitos de dados.

As threads em Java cooperam entre si por meio de mecanismos como sincronização e comunicação. A sincronização é usada para garantir que recursos compartilhados sejam acessados de forma segura por várias threads, enquanto a comunicação permite que as threads troquem dados e coordenem suas atividades.

Criando Threads em Java

Em Java, existem duas maneiras principais de criar threads: estendendo a classe Thread e implementando a interface Runnable.

Estendendo a classe Thread:

class MinhaThread extends Thread {
    public void run() {
        // Código a ser executado pela thread
    }
}

// Criando e iniciando a thread
MinhaThread thread = new MinhaThread();
thread.start();

Implementando a interface Runnable:

class MinhaRunnable implements Runnable {
    public void run() {
        // Código a ser executado pela thread
    }
}

// Criando uma instância de MinhaRunnable
MinhaRunnable minhaRunnable = new MinhaRunnable();
// Criando e iniciando a thread
Thread thread = new Thread(minhaRunnable);
thread.start();

Ciclo de Vida de uma Thread

Uma thread em Java passa por diversos estados durante seu ciclo de vida:

  • NEW: Representa um tópico de thread que foi criado, mas ainda não foi iniciado.
  • RUNNABLE: Indica um thread que está atualmente em execução na Máquina Virtual Java (JVM).
  • BLOCKED: Refere-se a um thread que foi bloqueado para um monitor por outro thread e está aguardando para obter acesso ao monitor.
  • WAITING: Indica um thread que está aguardando por um período de tempo não especificado para que outro(s) thread(s) conclua(m) uma ação específica.
  • TIMED_WAITING: Representa um thread que está aguardando por um período de tempo determinado para que outro thread conclua uma ação.
  • TERMINATED: Refere-se a um thread que foi encerrado e está neste estado após a conclusão de sua execução.
threads em java

Exemplo 1: Thread que imprime números de 1 a 5

class Contador extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000); // Aguarda 1 segundo
            } catch (InterruptedException e) {
                System.err.println("Thread interrompida");
            }
        }
    }

    public static void main(String[] args) {
        Contador contador = new Contador();
        contador.start();
    }
}

Exemplo 2: Thread que executa uma tarefa de impressão

class TarefaImprimir implements Runnable {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("Imprimindo documento " + i);
            try {
                Thread.sleep(1000); // Aguarda 1 segundo
            } catch (InterruptedException e) {
                System.err.println("Thread interrompida");
            }
        }
    }

    public static void main(String[] args) {
        TarefaImprimir tarefa = new TarefaImprimir();
        Thread thread = new Thread(tarefa);
        thread.start();
    }
}

Exemplo 3: Thread que simula um contador regressivo

class ContadorRegressivo extends Thread {
    public void run() {
        for (int i = 5; i > 0; i--) {
            System.out.println("Contagem regressiva: " + i);
            try {
                Thread.sleep(1000); // Aguarda 1 segundo
            } catch (InterruptedException e) {
                System.err.println("Thread interrompida");
            }
        }
        System.out.println("Fim da contagem regressiva!");
    }

    public static void main(String[] args) {
        ContadorRegressivo contador = new ContadorRegressivo();
        contador.start();
    }
}

Exemplo 4: Processamento de Imagens

Suponha que você tenha uma aplicação de processamento de imagens que precisa aplicar filtros em várias imagens de forma simultânea.

Cada imagem pode ser processada por uma thread separada para melhorar o desempenho.

package br.com.virandoprogramador.threads

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class ProcessamentoImagem extends Thread {
    private String nomeArquivo;
  
  public static void main(String[] args) {
        String[] arquivos = {"imagem1.jpg", "imagem2.jpg", "imagem3.jpg"};

        for (String arquivo : arquivos) {
            Thread thread = new ProcessamentoImagem(arquivo);
            thread.start();
        }
    }

    public ProcessamentoImagem(String nomeArquivo) {
        this.nomeArquivo = nomeArquivo;
    }

    public void run() {
        // Código para processar a imagem
        // Exemplo: aplicar filtro
        BufferedImage imagem = carregarImagem(nomeArquivo);
        BufferedImage imagemProcessada = aplicarFiltro(imagem);
        salvarImagem(imagemProcessada, "processado_" + nomeArquivo);
    }

    private BufferedImage carregarImagem(String nomeArquivo) {
        // Carregar a imagem do disco
        // Código omitido
        return null;
    }

    private BufferedImage aplicarFiltro(BufferedImage imagem) {
        // Aplicar filtro na imagem
        // Código omitido
        return null;
    }

    private void salvarImagem(BufferedImage imagem, String nomeArquivo) {
        // Salvar a imagem processada no disco
        // Código omitido
    }
}

Exemplo 5: Servidor de Chat Multithreaded

Um servidor de chat que atende múltiplos clientes simultaneamente é outro exemplo comum de uso de threads em Java.

package br.com.virandoprogramador.threads

import java.io.*;
import java.net.*;
import java.util.*;

public class ServidorChat {
    private static final int PORTA = 12345;
    private static Set<PrintWriter> clientes = new HashSet<>();

    public static void main(String[] args) {
        try (ServerSocket servidor = new ServerSocket(PORTA)) {
            System.out.println("Servidor de chat iniciado na porta " + PORTA);
            while (true) {
                new ThreadCliente(servidor.accept()).start();
            }
        } catch (IOException e) {
            System.err.println("Erro no servidor: " + e.getMessage());
        }
    }

    private static class ThreadCliente extends Thread {
        private Socket socket;
        private PrintWriter escritor;

        public ThreadCliente(Socket socket) {
            this.socket = socket;
        }

        public void run() {
            try {
                BufferedReader leitor = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                escritor = new PrintWriter(socket.getOutputStream(), true);
                clientes.add(escritor);

                String mensagem;
                while ((mensagem = leitor.readLine()) != null) {
                    for (PrintWriter cliente : clientes) {
                        cliente.println(mensagem);
                    }
                }
            } catch (IOException e) {
                System.err.println("Erro ao processar cliente: " + e.getMessage());
            } finally {
                if (escritor != null) {
                    clientes.remove(escritor);
                }
                try {
                    socket.close();
                } catch (IOException e) {
                    // Ignorar
                }
            }
        }
    }
}

Estes são apenas dois exemplos de uso de threads em Java.

As threads podem ser aplicadas em uma ampla variedade de situações para melhorar o desempenho, a escalabilidade e a responsividade de uma aplicação.

Programação Paralela

A programação paralela é uma técnica que envolve a execução simultânea de múltiplas tarefas para resolver um problema maior. Em Java, a programação paralela é realizada principalmente através do uso de threads.

Tipos de Programação Paralela:

  1. Paralelismo de Dados: Nesse modelo, tarefas independentes são executadas simultaneamente em diferentes conjuntos de dados. Por exemplo, em um aplicativo de processamento de imagens, várias threads podem ser usadas para aplicar filtros diferentes em diferentes partes da imagem ao mesmo tempo.
  2. Paralelismo de Tarefa: Aqui, o problema é dividido em subtarefas menores, que são executadas simultaneamente por diferentes threads. As saídas dessas subtarefas são então combinadas para produzir o resultado final. Um exemplo disso é a divisão de uma grande tarefa de cálculo em várias partes menores, que são então distribuídas entre várias threads para processamento paralelo.

Em Java, a API java.util.concurrent fornece suporte para programação paralela, oferecendo estruturas de dados e utilitários para facilitar o desenvolvimento de aplicações concorrentes e paralelas de forma segura e eficiente.

Em resumo, as threads em Java são uma ferramenta poderosa para a programação paralela, permitindo a execução simultânea de tarefas e a resolução eficiente de problemas complexos.

Ao compreender os conceitos fundamentais de threads e programação paralela, os desenvolvedores podem criar aplicações mais responsivas e eficientes.

Conclusão

As threads em Java são uma poderosa ferramenta para desenvolver aplicações concorrentes e multithreaded. No entanto, é essencial entender os conceitos básicos, como criar e gerenciar threads, o ciclo de vida de uma thread e técnicas de sincronização, para escrever código seguro e eficiente.

Este guia fornece uma introdução abrangente ao mundo das threads em Java, mas há muito mais a ser explorado. Continue praticando e aprofundando seus conhecimentos para dominar completamente a programação concorrente em Java.

Avalie o Conteúdo
Compartilhar:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *