Introduction aux outils de concurrence Java

Aperçu

Dans le package de concurrence JDK (java.util.concurrent), nous nous avons fourni plusieurs classes d'outils de concurrence très importantes, à savoir CountDownLatch, CyclicBarrier, Semaphore et Exchanger.

Premier résumé

1. CountDownLatch, c'est une méthode de compteur pour assurer la synchronisation des threads, il ne contrôle pas le contexte entre plusieurs sous-threads, mais garantit seulement qu'un certain thread peut s'exécuter après que ces sous-threads sont exécutés.

2. CyclicBarrier, qui permet la synchronisation multi-thread en définissant une barrière, peut contrôler plusieurs threads à la barrière et d'autres threads s'exécutent également jusqu'au point de barrière, et peut réaliser les fonctions de CountDownLatch, mais il est plus puissant que CountDownLatch;

3. Sémaphore, sémaphore, utilisé pour contrôler le nombre de threads simultanés qui accèdent à une ressource publique;

4. Echangeur, utilisé pour l'échange de données entre deux threads.

 

introduction

1) CountDownLatch

CountDownLatch, similaire à un compteur, est utilisé pour attendre qu'un ou plusieurs threads terminent l'opération et démarrent l'exécution de son propre code.

Son constructeur reçoit un entier de type int comme compteur. Par exemple, si vous voulez attendre la fin de l'exécution de N threads, passez-lui N. Chaque fois que la fonction countDown est appelée, cela signifie qu'un certain thread a fini de s'exécuter. En fait, ce N n'est pas lié à des threads, ce qui signifie qu'il n'est pas nécessairement le même que le nombre de threads. Il n'a besoin que d'exécuter la fonction countDown N fois, et le thread actuellement en attente commencera à s'exécuter. Le code spécifique est répertorié ci-dessous:

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");
        }
    }

La sortie est la suivante:

1
2
3
Process finished with exit code 0

point important

1. Si le paramètre passé est supérieur à 2, le thread principal attendra indéfiniment.

2. Le compteur doit être supérieur à 0. S'il est égal à 0, l'appel de la méthode await ne bloquera pas le thread en cours.

Scénarios d'application

Lorsque vous rencontrez une tâche relativement chronophage avec une grande quantité de calcul, nous pouvons envisager d'utiliser le multi-threading pour opérer, diviser une grande tâche en plusieurs petites tâches (une tâche équivaut à un thread), lorsque chaque petite tâche Après la tâche est exécuté et le résultat est retourné, un certain thread principal effectue des statistiques sur le résultat.

2) CyclicBarrier

CyclicBarrier est une barrière de synchronisation. Sa fonction principale est de permettre à un groupe de threads d'atteindre une barrière (également appelée point de synchronisation) à bloquer. Jusqu'à ce que le dernier thread atteigne la barrière, la barrière sera ouverte et tous les threads interceptés continueront à exécuter.

Par défaut, son constructeur reçoit également un paramètre de type int N comme nombre de threads interceptés par la barrière. Chaque thread appelle la méthode await pour indiquer qu'il a atteint le point de barrière et est ensuite bloqué. Des exemples spécifiques sont les suivants:

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()));
        }
    }
La sortie est la suivante:
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

point important

1. Le N dans le constructeur doit être le nombre total de threads. Lorsque le dernier thread appelle la méthode await (atteignant la barrière), la barrière sera ouverte et le thread bloqué s'exécutera. La signification de N ici est la signification de CountDownLatch. N n'est pas le même.

2. Nous avons constaté que lorsque tous les threads atteignent la barrière, lorsque la barrière est ouverte, quel thread sera exécuté en premier? La réponse au code ci-dessus est incertaine. Mais CyclicBarrier nous fournit une utilisation plus avancée, c'est-à-dire que le constructeur prend également en charge le passage d'un objet Runnable.Lorsque la barrière est ouverte, la méthode run dans Runnable est exécutée en premier. (Cette fonction est très puissante et peut remplacer complètement CountDownLatch)

Scénarios d'application

Identique à CountDownLatch

Différence avec CountDownLatch:

Le compteur CountDownLatch ne peut être utilisé qu'une seule fois, tandis que le compteur CyclicBarrier peut être réinitialisé à l'aide de la méthode de réinitialisation, il convient donc à des scénarios d'entreprise plus complexes.

3) Sémaphore

Le sémaphore est un sémaphore, qui est principalement utilisé pour contrôler le nombre de threads qui accèdent simultanément à des ressources spécifiques et pour coordonner l'utilisation raisonnable des ressources communes par chaque thread.

Le constructeur reçoit également un paramètre de type int N en tant que paramètre d'entrée, qui est utilisé pour limiter le nombre maximum de threads simultanés qui accèdent à une certaine ressource publique, obtiennent une licence via l'acquisition et libèrent une licence.

Des exemples spécifiques sont les suivants:

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();
                }
            }
        }
    }
La sortie est la suivante:
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

point important

1. Il ressort de la sortie que le nombre simultané de threads est de 2. Lorsqu'un thread termine son exécution, le thread suivant obtient la ressource.

Scénarios d'application

Lorsque nous avons un grand nombre de threads accomplissant une tâche énorme, mais qu'une ressource publique a une arborescence de liens qui limite le thread, nous devons contrôler l'accès de ces grands nombres de threads à cette ressource commune. Par exemple, lorsque nous avons des centaines de threads qui doivent traiter les fichiers de données de G sur le local, une fois que chaque traitement de thread est terminé, les résultats doivent être écrits dans la base de données et la base de données ne prend en charge que les liens simultanés de dix threads. À ce stade, nous allons créer un lien vers la base de données. Le nombre maximum de connexions peut être contrôlé via Semaphore.

4, échangeur

Echanger (Exchanger), c'est un outil de collaboration utilisé entre threads, principalement utilisé pour l'échange de données entre threads. Il fournit un point de synchronisation auquel deux threads peuvent échanger des données entre eux. Regardez la démo spécifique ci-dessous:

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();
        }
    }

La sortie est la suivante:

---bababa
a is aaaaaaaaaa , b is bbbbbbbb

Process finished with exit code 0

point important

1. L'échangeur ne peut agir qu'entre deux fils.S'il agit sur le troisième fil, c'est que le troisième fil est en attente;

2. Il y a aussi une fonction surchargée en échange, qui reçoit un temps d'attente pour éviter d'attendre tout le temps.

références

"L'art de la programmation simultanée en Java"

Je suppose que tu aimes

Origine blog.csdn.net/qq_27828675/article/details/114068266
conseillé
Classement