Maison > Java > javaDidacticiel > le corps du texte

Introduction à l'utilisation des trois classes auxiliaires CountDownLatch, CyclicBarrier et Semaphore en Java

不言
Libérer: 2019-02-19 15:56:48
avant
2533 Les gens l'ont consulté

Cet article vous présente une introduction à l'utilisation des trois classes auxiliaires CountDownLatch, CyclicBarrier et Semaphore en Java. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Dans Java 1.5, certaines classes auxiliaires très utiles sont fournies pour nous aider dans la programmation simultanée, telles que CountDownLatch, CyclicBarrier et Semaphore. Aujourd'hui, nous allons apprendre l'utilisation de ces trois classes auxiliaires.

1. Utilisation de CountDownLatch

La classe CountDownLatch est située sous le package java.util.concurrent, qui peut être utilisée pour implémenter des fonctions de type compteur. Par exemple, il existe une tâche A qui doit attendre la fin des quatre autres tâches avant de pouvoir être exécutée. À ce stade, vous pouvez utiliser CountDownLatch pour implémenter cette fonction.

La classe CountDownLatch ne fournit qu'un seul constructeur :

public CountDownLatch(int count) {  };  //参数count为计数值
Copier après la connexion

Ensuite, les trois méthodes suivantes sont les méthodes les plus importantes de la classe CountDownLatch :

public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { };  //将count值减1
Copier après la connexion

Voir ci-dessous Avec un exemple, tout le monde connaîtra l'utilisation de CountDownLatch :

public class Test {
     public static void main(String[] args) {  
         final CountDownLatch latch = new CountDownLatch(2);
 
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                    Thread.sleep(3000);
                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
 
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                     Thread.sleep(3000);
                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
 
         try {
             System.out.println("等待2个子线程执行完毕...");
            latch.await();
            System.out.println("2个子线程已经执行完毕");
            System.out.println("继续执行主线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }
}
Copier après la connexion

Résultat de l'exécution :

线程Thread-0正在执行
线程Thread-1正在执行
等待2个子线程执行完毕...
线程Thread-0执行完毕
线程Thread-1执行完毕
2个子线程已经执行完毕
继续执行主线程
Copier après la connexion

2. Utilisation de CyclicBarrier

Cela signifie littéralement une clôture de bouclage, à travers laquelle un groupe de threads peut être amené à attendre un certain état avant de tous s'exécuter en même temps. C'est ce qu'on appelle le bouclage car CyclicBarrier peut être réutilisé une fois que tous les threads en attente sont libérés. Appelons cette barrière d'état pour le moment. Lorsque la méthode wait() est appelée, le thread est en barrière.

La classe CyclicBarrier se trouve sous le package java.util.concurrent. CyclicBarrier fournit 2 constructeurs :

public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
Copier après la connexion

Le paramètre parties fait référence au nombre de threads ou de tâches autorisés à attendre le résultat. état de barrière ; le paramètre barrièreAction est ce qui sera exécuté lorsque ces threads atteindront l’état de barrière.

Ensuite, la méthode la plus importante dans CyclicBarrier est la méthode wait, qui a 2 versions surchargées :

public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
Copier après la connexion

La première version est plus couramment utilisée et sert à suspendre le thread en cours jusqu'à ce que tout soit terminé. Les threads atteignent tous l'état de barrière puis effectuent les tâches suivantes en même temps

La deuxième version consiste à laisser ces threads attendre un certain temps s'il y a encore des threads qui n'ont pas atteint la barrière. état, laissez directement les threads qui ont atteint la barrière effectuer les tâches suivantes.

Quelques exemples vous aideront à comprendre :

Supposons qu'il y ait plusieurs threads qui doivent écrire des données, et seulement après que tous les threads ont terminé l'opération d'écriture des données, ces threads peuvent continuer à faire le suivant À ce stade, vous pouvez utiliser CyclicBarrier :

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}
Copier après la connexion

Résultats de l'exécution :

线程Thread-0正在写入数据...
线程Thread-3正在写入数据...
线程Thread-2正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
Copier après la connexion

Comme le montrent les résultats de sortie ci-dessus, chaque thread d'écriture a terminé l'opération d'écriture des données Après cela, il attend que d’autres threads terminent l’opération d’écriture.

Une fois toutes les opérations d'écriture de thread terminées, tous les threads continueront à effectuer les opérations suivantes.

Si vous souhaitez effectuer des opérations supplémentaires une fois que tous les threads ont terminé les opérations d'écriture, vous pouvez fournir des paramètres Runnable pour CyclicBarrier :

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程"+Thread.currentThread().getName());  
            }
        });
 
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}
Copier après la connexion

Résultats d'exécution :

线程Thread-0正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2正在写入数据...
线程Thread-3正在写入数据...
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
当前线程Thread-3
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
Copier après la connexion

Il ressort des résultats que lorsque les quatre threads atteignent l'état de barrière, un thread sera sélectionné parmi les quatre threads pour exécuter le Runnable.

Jetons un coup d'œil à l'effet de la spécification de l'heure d'attente :

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
 
        for(int i=0;i<N;i++) {
            if(i<N-1)
                new Writer(barrier).start();
            else {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                new Writer(barrier).start();
            }
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                try {
                    cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
                } catch (TimeoutException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"所有线程写入完毕,继续处理其他任务...");
        }
    }
}
Copier après la connexion

Résultats de l'exécution :

线程Thread-0正在写入数据...
线程Thread-2正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-3正在写入数据...
java.util.concurrent.TimeoutException
Thread-1所有线程写入完毕,继续处理其他任务...
Thread-0所有线程写入完毕,继续处理其他任务...
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
java.util.concurrent.BrokenBarrierException
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
java.util.concurrent.BrokenBarrierException
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
Thread-2所有线程写入完毕,继续处理其他任务...
java.util.concurrent.BrokenBarrierException
线程Thread-3写入数据完毕,等待其他线程写入完毕
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
Thread-3所有线程写入完毕,继续处理其他任务...
Copier après la connexion

Le code ci-dessus est délibérément utilisé dans le for boucle de la méthode principale. Le dernier thread démarre avec retard car une fois que les trois premiers threads ont atteint la barrière, après avoir attendu le temps spécifié et constaté que le quatrième thread n'a pas atteint la barrière, une exception est levée et les tâches suivantes continuent. être exécuté.

De plus, CyclicBarrier peut être réutilisé. Jetez un œil à l'exemple suivant :

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
 
        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
 
        try {
            Thread.sleep(25000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        System.out.println("CyclicBarrier重用");
 
        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
 
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"所有线程写入完毕,继续处理其他任务...");
        }
    }
}
Copier après la connexion

Résultats de l'exécution :

线程Thread-0正在写入数据...
线程Thread-1正在写入数据...
线程Thread-3正在写入数据...
线程Thread-2正在写入数据...
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
Thread-0所有线程写入完毕,继续处理其他任务...
Thread-3所有线程写入完毕,继续处理其他任务...
Thread-1所有线程写入完毕,继续处理其他任务...
Thread-2所有线程写入完毕,继续处理其他任务...
CyclicBarrier重用
线程Thread-4正在写入数据...
线程Thread-5正在写入数据...
线程Thread-6正在写入数据...
线程Thread-7正在写入数据...
线程Thread-7写入数据完毕,等待其他线程写入完毕
线程Thread-5写入数据完毕,等待其他线程写入完毕
线程Thread-6写入数据完毕,等待其他线程写入完毕
线程Thread-4写入数据完毕,等待其他线程写入完毕
Thread-4所有线程写入完毕,继续处理其他任务...
Thread-5所有线程写入完毕,继续处理其他任务...
Thread-6所有线程写入完毕,继续处理其他任务...
Thread-7所有线程写入完毕,继续处理其他任务...
Copier après la connexion

Comme le montre l'exécution. résultats, dans le premier 4 Après qu'un fil ait franchi l'état barrière, il peut être utilisé pour un nouveau cycle d'utilisation. CountDownLatch ne peut pas être réutilisé.

3. Utilisation du sémaphore

Sémaphore est traduit littéralement par sémaphore peut contrôler le nombre de threads accessibles en même temps et obtenir une autorisation via acquire(). Si ce n'est pas le cas, attendez et release() libère une autorisation.

La classe Semaphore est située sous le package java.util.concurrent. Elle fournit 2 constructeurs :

public Semaphore(int permits) {          //参数permits表示许可数目,即同时可以允许多少线程进行访问
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {    //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
Copier après la connexion

Parlons de certaines des méthodes les plus importantes de la classe Semaphore. est les méthodes acquire (), release() :

public void acquire() throws InterruptedException {  }     //获取一个许可
public void acquire(int permits) throws InterruptedException { }    //获取permits个许可
public void release() { }          //释放一个许可
public void release(int permits) { }    //释放permits个许可
Copier après la connexion

acquire() est utilisée pour obtenir une autorisation. Si aucune autorisation ne peut être obtenue, il attendra que l'autorisation soit obtenue.

release() est utilisé pour libérer l'autorisation. Notez que l’autorisation doit être obtenue avant de pouvoir être publiée.

Ces quatre méthodes seront bloquées. Si vous souhaitez obtenir le résultat de l'exécution immédiatement, vous pouvez utiliser les méthodes suivantes :

public boolean tryAcquire() { };    //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };  //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
Copier après la connexion

De plus, vous pouvez également obtenir le nombre de disponibles. autorisations via la méthode availablePermits().

Regardons l'utilisation spécifique du Sémaphore à travers un exemple :

Si une usine dispose de 5 machines mais de 8 ouvriers, une machine ne peut être utilisée que par un seul ouvrier à la fois. Après l’avoir utilisé, d’autres travailleurs peuvent continuer à l’utiliser. Ensuite, nous pouvons y parvenir grâce au Sémaphore :

public class Test {
    public static void main(String[] args) {
        int N = 8;            //工人数
        Semaphore semaphore = new Semaphore(5); //机器数目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
 
    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }
 
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一个机器在生产...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"释放出机器");
                semaphore.release();          
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Copier après la connexion

Résultat de l'exécution :

工人0占用一个机器在生产...
工人1占用一个机器在生产...
工人2占用一个机器在生产...
工人4占用一个机器在生产...
工人5占用一个机器在生产...
工人0释放出机器
工人2释放出机器
工人3占用一个机器在生产...
工人7占用一个机器在生产...
工人4释放出机器
工人5释放出机器
工人1释放出机器
工人6占用一个机器在生产...
工人3释放出机器
工人7释放出机器
工人6释放出机器
Copier après la connexion

Ce qui suit est un résumé des trois classes auxiliaires mentionnées ci-dessus :

1) CountDownLatch et CyclicBarrier peuvent réaliser une attente entre les threads, mais ils ont des objectifs différents :

CountDownLatch est généralement utilisé pour qu'un thread A attende que plusieurs autres threads terminent leurs tâches avant de l'exécuter

Et CyclicBarrier est généralement utilisé pour qu'un groupe de threads s'attendent les uns les autres pour atteindre un certain ; state, puis ce groupe Les threads sont exécutés simultanément

De plus, CountDownLatch ne peut pas être réutilisé, mais CyclicBarrier peut être réutilisé.

2) Le sémaphore est en fait un peu similaire à un verrou. Il est généralement utilisé pour contrôler l'accès à un certain groupe de ressources.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:cnblogs.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal