JavaRush /Blogue Java /Random-PT /Você não pode estragar o Java com um Tópico: Parte VI – A...
Viacheslav
Nível 3

Você não pode estragar o Java com um Tópico: Parte VI – Até a barreira!

Publicado no grupo Random-PT

Introdução

Streams são uma coisa interessante. Nas análises anteriores, examinamos algumas das ferramentas disponíveis para implementar multithreading. Vamos ver que mais coisas interessantes podemos fazer. Neste ponto sabemos muito. Por exemplo, em “ Você não pode estragar Java com um Thread: Parte I - Threads ”, sabemos que um thread é um Thread. Sabemos que um thread está executando alguma tarefa. Se quisermos que nossa tarefa possa ser executada ( run), devemos especificar o thread como um determinado Runnable. Você não pode estragar o Java com um Tópico: Parte VI – Até a barreira!  - 1Para lembrar, podemos usar o Tutorialspoint Java Online 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 um conceito como bloqueio. Lemos sobre isso em “ Você não pode estragar o Java com um thread: Parte II – Sincronização ”. Um thread pode ocupar um bloqueio e então outro thread que tentar ocupar o bloqueio será forçado a esperar que o bloqueio seja liberado:
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 o que mais podemos fazer de interessante.

Semáforos

O meio mais simples de controlar quantos threads podem funcionar simultaneamente é um semáforo. Como na ferrovia. A luz verde está acesa - você pode. A luz vermelha está acesa - estamos esperando. O que esperamos de um semáforo? Permissões. Permissão em inglês - permissão. Para obter permissão, você precisa obtê-la, que em inglês será adquirida. E quando a permissão não for mais necessária, devemos distribuí-la, ou seja, liberá-la ou nos livrar dela, o que em inglês será release. Vamos ver como isso funciona. Precisaremos importar a classe java.util.concurrent.Semaphore. 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 podemos ver, depois de memorizar palavras em inglês, entendemos como funciona o semáforo. Curiosamente, a principal condição é que a “conta” do semáforo tenha um número positivo de permissões. Portanto, você pode iniciá-lo com menos. E você pode solicitar (adquirir) mais de 1.

Trava de contagem regressiva

O próximo mecanismo é CountDownLatch. CountDown em inglês é uma contagem regressiva e Latch é um parafuso ou trava. Ou seja, se traduzirmos, então esta é uma trava com contagem regressiva. Aqui precisamos da importação apropriada da classe java.util.concurrent.CountDownLatch. É como uma corrida ou corrida onde todos se reúnem na linha de largada e quando todos estão prontos, é dada permissão e todos largam ao mesmo tempo. 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();
 	}
}
aguardar em inglês - esperar. Ou seja, falamos primeiro countDown. Como diz o Google Translator, contagem regressiva é “um ato de contar numerais em ordem inversa até zero”, ou seja, realizar uma ação de contagem regressiva, cujo objetivo é contar até zero. E então dizemos await- isto é, espere até que o valor do contador se torne zero. É interessante que tal contador seja descartável. Como é dito no JavaDoc - "Quando threads devem fazer contagem regressiva repetidamente desta forma, em vez disso use um CyclicBarrier", ou seja, se você precisar de uma contagem reutilizável, você precisa usar outra opção, que é chamada CyclicBarrier.

Barreira Cíclica

Como o nome sugere, CyclicBarrieré uma barreira cíclica. Precisaremos importar a classe java.util.concurrent.CyclicBarrier. Vejamos um exemplo:
public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("На старт!");
	CyclicBarrier berrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			berrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + berrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
Como você pode ver, o thread está em execução await, ou seja, aguardando. Neste caso, o valor da barreira diminui. A barreira é considerada quebrada ( berrier.isBroken()) quando a contagem regressiva chega a zero. Para redefinir a barreira, você precisa ligar para berrier.reset(), que estava faltando em CountDownLatch.

Permutador

O próximo remédio é Exchanger. Exchange do inglês é traduzido como exchange ou exchange. A Exchangeré um trocador, ou seja, algo através do qual eles trocam. Vejamos um exemplo 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() + " обменялся с " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
Aqui lançamos dois tópicos. Cada um deles executa o método de troca e espera que outro thread também execute o método de troca. Assim, os threads irão trocar os argumentos passados ​​entre si. Coisa interessante. Ela não te lembra nada? E ele lembra SynchronousQueue, que está no cerne de cachedThreadPool'a. Para maior clareza, aqui está 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 ao lançar um novo thread, este thread entrará em modo de espera, pois a fila estará vazia. E então maino tópico irá enfileirar o texto “Mensagem”. Ao mesmo tempo, ele irá parar pelo tempo necessário até receber este elemento de texto da fila. Neste tópico você também pode ler " SynchronousQueue Vs Exchanger ".

Phaser

E finalmente, a coisa mais fofa - Phaser. Precisaremos importar a classe java.util.concurrent.Phaser. Vejamos um exemplo simples:
public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // Вызывая метод register, мы регистрируем текущий поток (main) How участника
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // Через 3 секунды прибываем к барьеру и снимаемся регистрацию. Кол-во прибывших = кол-во регистраций = пуск
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // Говорим, что будет +1 участник на Phaser
        phaser.register();
        // Запускаем новый поток
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); //threads register arrival to the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
O exemplo mostra que a barreira, ao usar Phaser'a, é quebrada quando o número de inscrições coincide com o número de chegadas à barreira. Você pode saber mais Phaserno artigo do hub " Novo sincronizador Phaser ".

Resultados

Como você pode ver nos exemplos, existem diferentes maneiras de sincronizar threads. Anteriormente tentei lembrar algo sobre multithreading, espero que as partes anteriores tenham sido úteis. Dizem que o caminho para o multithreading começa com o livro “Java Concurrency in Practice”. Embora tenha sido lançado em 2006, as pessoas respondem que o livro é bastante fundamental e ainda assim impactante. Por exemplo, você pode ler as discussões aqui: " A simultaneidade Java na prática ainda é válida? ". Também é útil ler os links da discussão. Por exemplo, há um link para o livro " The Well-Grounded Java Developer ", no qual vale a pena prestar atenção ao " Capítulo 4. Simultaneidade moderna ". Há outra revisão completa sobre o mesmo tópico: “ A co-moeda Java na prática ainda é relevante na era do Java 8 ”. Também traz dicas sobre o que mais você deve ler para realmente entender o assunto. Depois disso, você pode dar uma olhada em um livro maravilhoso como " OCA OCP JavaSE 8 Programmer Practice Tests ". Estamos interessados ​​na segunda parte, ou seja, OCP. E existem testes em "∫". Este livro contém perguntas e respostas com explicações. Por exemplo: Você não pode estragar o Java com um Tópico: Parte VI – Até a barreira!  - 3Muitos podem começar a dizer que esta é apenas mais uma memorização de métodos. Por um lado, sim. Por outro lado, esta questão pode ser respondida lembrando que ExecutorServicese trata de uma espécie de “upgrade” Executor. E Executorpretende simplesmente ocultar o método de criação de threads, mas não a forma principal de executá-las, ou seja, rodar em uma nova thread Runnable. Portanto, execute(Callable)não, porque eles simplesmente adicionaram métodos ExecutorServiceque podem retornar . Como você pode ver, podemos memorizar uma lista de métodos, mas é muito mais fácil adivinhar se conhecermos a natureza das próprias classes. Bem, alguns materiais adicionais sobre o tema: ExecutorsubmitFuture #Viacheslav
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION