CodeGym /Blogue Java /Random-PT /Melhor juntos: Java e a classe Thread. Parte VI — Atire!
John Squirrels
Nível 41
San Francisco

Melhor juntos: Java e a classe Thread. Parte VI — Atire!

Publicado no grupo Random-PT

Introdução

Tópicos são uma coisa interessante. Em análises anteriores, analisamos algumas das ferramentas disponíveis para implementar o multithreading. Vamos ver que outras coisas interessantes podemos fazer. Neste ponto, sabemos muito. Por exemplo, de " Melhor juntos: Java e a classe Thread. Parte I — Threads de execução ", sabemos que a classe Thread representa um thread de execução. Sabemos que um thread executa alguma tarefa. Se quisermos que nossas tarefas sejam capazes de run, então devemos marcar o thread com Runnable. Melhor juntos: Java e a classe Thread.  Parte VI — Atire!  - 1Para lembrar, podemos usar o Tutorialspoint Online Java Compiler :

public static void main(String[] args){
	Runnable task = () -> {
 		Thread thread = Thread.currentThread();
		System.out.println("Hello from " + thread.getName());
	};
	Thread thread = new Thread(task);
	thread.start();
}
Também sabemos que temos algo chamado bloqueio. Aprendemos sobre isso em " Melhor juntos: Java e a classe Thread. Parte II — Sincronização . Se um thread adquirir um bloqueio, outro thread que tentar adquirir o bloqueio será forçado a aguardar a liberação do bloqueio:

import java.util.concurrent.locks.*;

public class HelloWorld{
	public static void main(String []args){
		Lock lock = new ReentrantLock();
		Runnable task = () -> {
			lock.lock();
			Thread thread = Thread.currentThread();
			System.out.println("Hello from " + thread.getName());
			lock.unlock();
		};
		Thread thread = new Thread(task);
		thread.start();
	}
}
Acho que é hora de falar sobre outras coisas interessantes que podemos fazer.

Semáforos

A maneira mais simples de controlar quantas threads podem ser executadas simultaneamente é um semáforo. É como um sinal ferroviário. Verde significa prosseguir. Vermelho significa esperar. Esperar o quê do semáforo? Acesso. Para obter acesso, devemos adquiri-lo. E quando o acesso não for mais necessário, devemos distribuí-lo ou liberá-lo. Vamos ver como isso funciona. Precisamos importar a java.util.concurrent.Semaphoreclasse. Exemplo:

public static void main(String[] args) throws InterruptedException {
	Semaphore semaphore = new Semaphore(0);
	Runnable task = () -> {
		try {
			semaphore.acquire();
			System.out.println("Finished");
			semaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	Thread.sleep(5000);
	semaphore.release(1);
}
Como você pode ver, essas operações (adquirir e liberar) nos ajudam a entender como funciona um semáforo. O mais importante é que, para obter acesso, o semáforo deve ter um número positivo de permissões. Essa contagem pode ser inicializada com um número negativo. E podemos solicitar (adquirir) mais de 1 licença.

CountDownLatch

O próximo mecanismo é CountDownLatch. Sem surpresa, esta é uma trava com uma contagem regressiva. Aqui precisamos da instrução de importação apropriada para a java.util.concurrent.CountDownLatchclasse. É como uma corrida a pé, onde todos se reúnem na linha de partida. E quando todos estiverem prontos, todos recebem o sinal de partida ao mesmo tempo e começam simultaneamente. Exemplo:

public static void main(String[] args) {
	CountDownLatch countDownLatch = new CountDownLatch(3);
	Runnable task = () -> {
		try {
			countDownLatch.countDown();
			System.out.println("Countdown: " + countDownLatch.getCount());
			countDownLatch.await();
			System.out.println("Finished");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
 	}
}
Primeiro, primeiro dizemos ao latch para countDown(). O Google define a contagem regressiva como "um ato de contar numerais na ordem inversa a zero". E então dizemos ao latch para await(), ou seja, espere até que o contador chegue a zero. Curiosamente, este é um contador único. A documentação do Java diz: "Quando os encadeamentos devem fazer uma contagem regressiva repetidamente dessa maneira, use um CyclicBarrier". Ou seja, se você precisa de um balcão reutilizável, precisa de uma opção diferente: CyclicBarrier.

CyclicBarrier

Como o nome indica, CyclicBarrieré uma barreira "reutilizável". Precisamos importar a java.util.concurrent.CyclicBarrierclasse. Vejamos um exemplo:

public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("On your mark!");
	CyclicBarrier barrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			barrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + barrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
Como você pode ver, a thread executa o awaitmétodo, ou seja, ela espera. Neste caso, o valor da barreira diminui. A barreira é considerada quebrada ( barrier.isBroken()) quando a contagem regressiva chega a zero. Para zerar a barreira, você precisa chamar o reset()método, que CountDownLatchnão possui.

Permutador

O próximo mecanismo é o Exchanger. Nesse contexto, uma troca é um ponto de sincronização onde as coisas mudam, são trocadas ou trocadas. Como seria de esperar, an Exchangeré uma classe que executa uma troca ou troca. Vejamos o exemplo mais simples:

public static void main(String[] args) {
	Exchanger<String> exchanger = new Exchanger<>();
	Runnable task = () -> {
		try {
			Thread thread = Thread.currentThread();
			String withThreadName = exchanger.exchange(thread.getName());
			System.out.println(thread.getName() + " exchanged with " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
Aqui iniciamos dois tópicos. Cada um deles executa o método exchange e espera que o outro thread também execute o método exchange. Ao fazer isso, os threads trocam os argumentos passados. Interessante. Não te lembra de algo? É uma reminiscência de SynchronousQueue, que está no coração de CachedThreadPool. Para maior clareza, segue um exemplo:

public static void main(String[] args) throws InterruptedException {
	SynchronousQueue<String> queue = new SynchronousQueue<>();
	Runnable task = () -> {
		try {
			System.out.println(queue.take());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	queue.put("Message");
}
O exemplo mostra que quando uma nova thread for iniciada, ela aguardará, pois a fila estará vazia. E então o thread principal coloca a string "Message" na fila. Além do mais, ele também irá parar até que esta string seja recebida da fila. Você também pode ler " SynchronousQueue vs Exchanger " para saber mais sobre este tópico.

Phaser

Guardamos o melhor para o final - Phaser. Precisamos importar a java.util.concurrent.Phaserclasse. Vejamos um exemplo simples:

public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // By calling the register method, we register the current (main) thread as a party
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // We indicate that there will be a +1 party on the Phaser
        phaser.register();
        // Start a new thread
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
O exemplo ilustra que ao usar Phaser, a barreira é quebrada quando o número de registros coincide com o número de chegadas na barreira. Você pode se familiarizar mais Phaserlendo este artigo GeeksforGeeks .

Resumo

Como você pode ver nesses exemplos, há várias maneiras de sincronizar threads. Anteriormente, tentei relembrar aspectos de multithreading. Espero que os capítulos anteriores desta série tenham sido úteis. Algumas pessoas dizem que o caminho para multithreading começa com o livro "Java Concurrency in Practice". Embora tenha sido lançado em 2006, as pessoas dizem que o livro é bastante fundamental e ainda é relevante hoje. Por exemplo, você pode ler a discussão aqui: "Java Concurrency In Practice" ainda é válido? . Também é útil ler os links na discussão. Por exemplo, há um link para o livro The Well-Grounded Java Developer , e faremos uma menção específica ao Capítulo 4. Concorrência moderna . Há também uma revisão completa sobre este tópico:A "simultaneidade de Java na prática" ainda é válida na era do Java 8? Esse artigo também oferece sugestões sobre o que mais ler para realmente entender esse tópico. Depois disso, você pode dar uma olhada em um ótimo livro como OCA/OCP Java SE 8 Programmer Practice Tests . Estamos interessados ​​na segunda sigla: OCP (Oracle Certified Professional). Você encontrará testes no "Capítulo 20: Simultaneidade Java". Este livro tem perguntas e respostas com explicações. Por exemplo: Melhor juntos: Java e a classe Thread.  Parte VI — Atire!  - 3Muitas pessoas podem começar dizendo que esta questão é mais um exemplo de memorização de métodos. Por um lado, sim. Por outro lado, você poderia responder a essa pergunta lembrando que ExecutorServiceé uma espécie de "upgrade" do Executor. EExecutordestina-se a simplesmente esconder a forma como as threads são criadas, mas não é a principal forma de executá-las, ou seja, iniciar um Runnableobjeto em uma nova thread. É por isso que não há execute(Callable)— porque em ExecutorService, o Executorsimplesmente adiciona submit()métodos que podem retornar um Futureobjeto. Claro, podemos memorizar uma lista de métodos, mas é muito mais fácil fazer nossa resposta com base em nosso conhecimento da natureza das próprias classes. E aqui estão alguns materiais adicionais sobre o tema: Melhor juntos: Java e a classe Thread. Parte I — Threads de execução Melhor juntos: Java e a classe Thread. Parte II — Sincronização melhor juntos: Java e a classe Thread. Parte III — Interação melhor juntos: Java e a classe Thread. Parte IV — Callable, Future e amigos Melhor juntos: Java e a classe Thread. Parte V — Executor, ThreadPool, Fork/Join
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION