Introdução às ferramentas de simultaneidade Java

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"

Acho que você gosta

Origin blog.csdn.net/qq_27828675/article/details/114068266
Recomendado
Clasificación