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
.
Para 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
main
o 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();
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
phaser.register();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance();
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
Phaser
no 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:
Muitos 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
ExecutorService
se trata de uma espécie de “upgrade”
Executor
. E
Executor
pretende 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
ExecutorService
que 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:
Executor
submit
Future
#Viacheslav
GO TO FULL VERSION