Visão geral
No pacote de simultaneidade JDK (java.util.concurrent), fornecemos várias classes de ferramentas de simultaneidade muito importantes, a saber, CountDownLatch, CyclicBarrier, Semaphore e Exchanger.
Primeiro resumo
1. CountDownLatch, é um método de contador para garantir a sincronização do encadeamento; ele não controla o contexto entre vários sub-encadeamentos, mas apenas garante que um determinado encadeamento pode ser executado depois que esses sub-encadeamentos são executados.
2. CyclicBarrier, que permite a sincronização multi-thread definindo uma barreira, pode controlar vários threads na barreira e outros threads também são executados no ponto de barreira e pode atingir as funções de CountDownLatch, mas é mais poderoso do que CountDownLatch;
3. Semáforo, semáforo, usado para controlar o número de threads simultâneos que acessam um recurso público;
4. Trocador, usado para troca de dados entre dois threads.
Introdução
1) CountDownLatch
CountDownLatch, semelhante a um contador, é usado para esperar que um ou mais threads concluam a operação e iniciem a execução de seu próprio código.
Seu construtor recebe um inteiro do tipo int como contador.Por exemplo, se você quiser esperar que N threads terminem de ser executados, passe N. Cada vez que a função countDown é chamada, significa que uma determinada thread terminou a execução. Na verdade, este N não está vinculado a threads, o que significa que não é necessariamente o mesmo que o número de threads. Ele só precisa executar a função de contagem regressiva N vezes, e a thread atualmente em espera começará a ser executada. O código específico está listado abaixo:
public static class CountDownLatchTest {
static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SleepUtils.second(2);
System.out.println("1");
countDownLatch.countDown();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SleepUtils.second(4);
System.out.println("2");
countDownLatch.countDown();
}
}).start();
try {
// 主线程开始等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3");
}
}
O resultado é o seguinte:
1
2
3
Process finished with exit code 0
ponto importante
1. Se o parâmetro passado for maior que 2, a thread principal esperará para sempre.
2. O contador deve ser maior que 0. Se for 0, chamar o método await não bloqueará o segmento atual.
Cenários de aplicação
Ao encontrar uma tarefa relativamente demorada com uma grande quantidade de cálculos, podemos considerar o uso de multi-threading para operar, dividir uma grande tarefa em várias pequenas tarefas (uma tarefa é equivalente a um thread), quando cada pequena tarefa Após a tarefa é executado e o resultado é retornado, uma certa thread principal executa estatísticas sobre o resultado.
2) CyclicBarrier
CyclicBarrier é uma barreira de sincronização. Sua função principal é permitir que um grupo de threads alcance uma barreira (também chamado de ponto de sincronização) para ser bloqueado. Até que a última thread alcance a barreira, a barreira será aberta e todos os threads interceptados continuarão para executar.
Por padrão, seu construtor também recebe um parâmetro de tipo int N como o número de threads interceptados pela barreira.Cada thread chama o método await para indicar que atingiu o ponto de barreira e, em seguida, está bloqueado. Os exemplos específicos são os seguintes:
public class CyclicBarrierTest {
// 参数表示屏障拦截的线程数量, 每个线程调用 await方法,告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
// 屏障拦截的线程数量必须和当前的线程数一致,并且都调用await方法,否则当前所有的线程都处于等待状态
static CyclicBarrier c = new CyclicBarrier(3);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("1---1 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
c.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1---2 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2---1 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
c.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2---2 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}).start();
SleepUtils.second(2);
System.out.println("3---1 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
c.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3---2 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
O resultado é o seguinte:
1---1 17:05:01
2---1 17:05:01
3---1 17:05:03
3---2 17:05:03
1---2 17:05:03
2---2 17:05:03
Process finished with exit code 0
ponto importante
1. O N no construtor deve ser o número total de threads. Quando a última thread chamar o método await (alcançando a barreira), a barreira será aberta e a thread bloqueada será executada. O significado de N aqui é o significado de CountDownLatch. N não é o mesmo.
2. Descobrimos que quando todos os threads alcançam a barreira, quando a barreira é aberta, qual thread será executado primeiro? A resposta ao código acima é incerta. Mas CyclicBarrier nos fornece um uso mais avançado, ou seja, o construtor também suporta a passagem de um objeto Runnable.Quando a barreira é aberta, o método run em Runnable é executado primeiro. (Esta função é muito poderosa e pode substituir completamente CountDownLatch)
Cenários de aplicação
Igual a CountDownLatch
Diferença de CountDownLatch:
O contador CountDownLatch pode ser usado apenas uma vez, enquanto o contador CyclicBarrier pode ser redefinido usando o método de redefinição, portanto, é adequado para cenários de negócios mais complexos.
3) Semaphore
Semáforo é um semáforo, que é usado principalmente para controlar o número de threads que acessam recursos específicos simultaneamente e para coordenar o uso razoável de recursos comuns por cada thread.
O construtor também recebe um parâmetro de tipo int N como um parâmetro de entrada, que é usado para limitar o número máximo de threads simultâneos que acessam um determinado recurso público, obtêm uma licença por meio da aquisição e liberam uma licença.
Os exemplos específicos são os seguintes:
public class SemaphoreTest {
private static final int THREAD_COUNT = 6;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new MyRunnable(i + 1));
}
threadPool.shutdown();
}
static class MyRunnable implements Runnable {
private int sleep;
public MyRunnable(int sleep) {
this.sleep = sleep;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("save data " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(sleep);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
O resultado é o seguinte:
save data 19:44:37
save data 19:44:37
save data 19:44:38
save data 19:44:39
save data 19:44:41
save data 19:44:43
Process finished with exit code 0
ponto importante
1. Pode-se descobrir na saída que o número simultâneo de threads é 2. Quando uma thread termina de ser executada, a próxima thread obtém o recurso.
Cenários de aplicação
Quando temos um grande número de threads completando uma tarefa enorme, mas um recurso público tem uma árvore de links que limita o thread, precisamos controlar o acesso desse grande número de threads a esse recurso comum. Por exemplo, quando temos centenas de threads que precisam processar os arquivos de dados de G no local, depois que cada processamento de thread é concluído, os resultados precisam ser gravados no banco de dados, e o banco de dados suporta apenas links simultâneos de dez threads. Neste momento faremos um link para o banco de dados.O número máximo de conexões pode ser controlado através do Semaphore.
4) Trocador
Exchanger (Exchanger), é uma ferramenta de colaboração utilizada entre threads, principalmente para troca de dados entre threads. Ele fornece um ponto de sincronização no qual dois threads podem trocar dados entre si. Veja a demonstração específica abaixo:
public class ExchangerTest {
private static final Exchanger<String> exchanger = new Exchanger<>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
String a = "aaaaaaaaaa";
try {
String b = exchanger.exchange(a);
System.out.println("---" + b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String b = "bbbbbbbb";
String a = exchanger.exchange("bababa");
System.out.println("a is " + a + " , b is " + b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadPool.shutdown();
}
}
O resultado é o seguinte:
---bababa
a is aaaaaaaaaa , b is bbbbbbbb
Process finished with exit code 0
ponto importante
1. O permutador só pode atuar entre dois fios.Se atuar no terceiro fio, o terceiro está esperando;
2. Há também uma função sobrecarregada na central, que recebe um tempo de espera para evitar a espera o tempo todo.
referências
"A arte da programação simultânea em Java"