Imprimir números pares e ímpares usando 2 linhas
1. Introdução
Neste tutorial, vamos dar uma olhada em como podemos imprimir números pares e ímpares usando dois threads.
O objetivo é imprimir os números em ordem, enquanto um segmento imprime apenas os números pares e o outro segmento imprime apenas os números ímpares. Estaremos usando os conceitos de sincronização de thread e comunicação entre threads para resolver o problema.
2. Threads em Java
Threads são processos leves que podem ser executados simultaneamente. A execução simultânea de vários encadeamentos pode ser boa em relação ao desempenho e à utilização da CPU, pois podemos trabalhar em mais de uma tarefa ao mesmo tempo em diferentes encadeamentos em execução paralela.
Mais informações sobre threads em Java podem ser encontradas nestearticle.
In Java, we can create a thread by either extending the Thread class or by implementing the Runnable interface. Em ambos os casos, substituímos o métodorun e escrevemos a implementação do thread nele.
Mais informações sobre como usar esses métodos para criar um thread podem ser encontradashere.
3. Sincronização de threads
Em um ambiente multithread, é possível que 2 ou mais threads acessem o mesmo recurso ao mesmo tempo. Isso pode ser fatal e levar a resultados errôneos. Para evitar isso, precisamos garantir que apenas um encadeamento acesse o recurso em um determinado momento.
Podemos conseguir isso usando a sincronização de threads.
Em Java, podemos marcar um método ou bloco como sincronizado, o que significa que apenas uma thread será capaz de entrar naquele método ou bloco em um determinado momento.
Mais detalhes sobre a sincronização de thread em Java podem ser encontrados emhere.
4. Comunicação entre segmentos
A comunicação entre segmentos permite que os segmentos sincronizados se comuniquem usando um conjunto de métodos.
Os métodos usados sãowait,notify,enotifyAll,, todos herdados da classeObject.
Wait() causes the current thread to wait indefinitely until some other thread calls notify() or notifyAll() on the same object. Podemos chamarnotify() para despertar threads que estão esperando para acessar o monitor deste objeto.
Mais detalhes sobre o funcionamento desses métodos podem ser encontradoshere.
5. Impressão de números pares e ímpares como alternativa
5.1. Usandowait() enotify()
Usaremos os conceitos discutidos de sincronização e comunicação entre threads para imprimir números pares e ímpares em ordem crescente, usando dois threads diferentes.
In the first step, we’ll implement the Runnable interface to define the logic of both threads. No métodorun, verificamos se o número é par ou ímpar.
Se o número for par, chamamos o métodoprintEven da classePrinter, senão chamamos o métodoprintOdd:
class TaskEvenOdd implements Runnable {
private int max;
private Printer print;
private boolean isEvenNumber;
// standard constructors
@Override
public void run() {
int number = isEvenNumber ? 2 : 1;
while (number <= max) {
if (isEvenNumber) {
print.printEven(number);
} else {
print.printOdd(number);
}
number += 2;
}
}
}
Definimos a classePrinter da seguinte forma:
class Printer {
private volatile boolean isOdd;
synchronized void printEven(int number) {
while (!isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = false;
notify();
}
synchronized void printOdd(int number) {
while (isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = true;
notify();
}
}
In the main method, we use the defined class to create two threads. Criamos um objeto da classePrinter e o passamos como o parâmetro para o construtorTaskEvenOdd:
public static void main(String... args) {
Printer print = new Printer();
Thread t1 = new Thread(new TaskEvenOdd(print, 10, false),"Odd");
Thread t2 = new Thread(new TaskEvenOdd(print, 10, true),"Even");
t1.start();
t2.start();
}
O primeiro thread será o thread ímpar, portanto, passamosfalse como o valor do parâmetroisEvenNumber. Para o segundo encadeamento, passamostrue. DefinimosmaxValue como 10 para ambos os threads, de forma que apenas os números de 1 a 10 sejam impressos.
Em seguida, iniciamos os dois threads chamando o métodostart(). Isso invocará o métodorun() de ambos os threads, conforme definido acima, em que verificamos se o número é ímpar ou par e os imprimimos.
Quando o thread ímpar começa a funcionar, o valor da variávelnumber será 1. Como é menor quemaxValuee o sinalizadorisEvenNumber é falso,printOdd() é chamado. No método, verificamos se o sinalizadorisOdd é verdadeiro e, embora seja verdadeiro, chamamoswait(). SinceisOdd é falso inicialmente,wait() não é chamado e o valor é impresso.
We then set the value of isOdd to true, so that the odd thread goes into the wait state and call notify() para ativar o segmento par. O thread par então acorda e imprime o número par, pois o sinalizadorodd é falso. Em seguida, ele chamanotify() para ativar o segmento ímpar.
O mesmo processo é realizado até que o valor da variávelnumber seja maior quemaxValue.
5.2. Usando semáforos
Um semáforo controla o acesso a um recurso compartilhado através do uso de um contador. Se ocounter is greater than zero, then access is allowed. Se for zero, o acesso será negado.
Java fornece a classeSemaphore no pacotejava.util.concurrent e podemos usá-la para implementar o mecanismo explicado. Mais detalhes sobre semáforos podem ser encontradoshere.
Criamos dois threads, um thread impar e um thread par. A linha ímpar imprimiria os números ímpares a partir de 1, e a linha par imprimirá os números pares a partir de 2.
Ambos os threads têm um objeto da classeSharedPrinter. The SharedPrinter class will have two semaphores, semOdd and semEven which will have 1 and 0 permits to start with. Isso garantirá que o número ímpar seja impresso primeiro.
Temos dois métodosprintEvenNum()eprintOddNum(). A thread ímpar chama o métodoprintOddNum()e a thread par chama o métodoprintEvenNum().
Para imprimir um número ímpar, o métodoacquire() é chamado emsemOdd, e como a permissão inicial é 1, adquire o acesso com sucesso, imprime o número ímpar e chamarelease() emsemEven.
Chamarrelease() aumentará a permissão em 1 parasemEven, e o segmento par pode então adquirir o acesso e imprimir o número par.
Este é o código para o fluxo de trabalho descrito acima:
public static void main(String[] args) {
SharedPrinter sp = new SharedPrinter();
Thread odd = new Thread(new Odd(sp, 10),"Odd");
Thread even = new Thread(new Even(sp, 10),"Even");
odd.start();
even.start();
}
class SharedPrinter {
private Semaphore semEven = new Semaphore(0);
private Semaphore semOdd = new Semaphore(1);
void printEvenNum(int num) {
try {
semEven.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semOdd.release();
}
void printOddNum(int num) {
try {
semOdd.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semEven.release();
}
}
class Even implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructor
@Override
public void run() {
for (int i = 2; i <= max; i = i + 2) {
sp.printEvenNum(i);
}
}
}
class Odd implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructors
@Override
public void run() {
for (int i = 1; i <= max; i = i + 2) {
sp.printOddNum(i);
}
}
}
6. Conclusão
Neste tutorial, vimos como podemos imprimir números pares e ímpares alternativamente usando dois threads em Java. Vimos dois métodos para obter os mesmos resultados:using wait() and notify()eusing a Semaphore.
E, como sempre, o código de trabalho completo está disponívelover on GitHub.