L'utilisation et l'analyse du code source de CountDownLatch
CountDownLatch est communément appelé verrou, qui permet à un ou plusieurs threads d'attendre que d'autres threads terminent l'opération spécifiée avant de s'exécuter.
Le constructeur de CountDownLatch accepte un paramètre de type int comme compteur. Si vous voulez attendre l'achèvement de N points, passez N ici.
Lorsque nous appelons la méthode countDown de CountDownLatch, N sera réduit de 1 et la méthode await de CountDownLatch bloquera le thread actuel jusqu'à ce que N devienne zéro.
Étant donné que la méthode countDown peut être utilisée n'importe où, les N points mentionnés ici peuvent être N threads ou N étapes d'exécution dans un thread (un thread peut compter à rebours plusieurs fois). Lorsqu'il est utilisé dans plusieurs threads, transmettez simplement la référence CountDownLatch au thread.
Méthodes dans CountDownLatch
Nom de la méthode | La description |
---|---|
CountDownLatch (nombre entier) | Construire un CountDownLatch avec un nombre donné |
void attendre () | Le thread actuel attend jusqu'à ce que le nombre de verrous atteigne zéro, sauf si le thread est interrompu |
boolean wait (long timeout, unité TimeUnit) | Le thread actuel attend jusqu'à ce que le nombre de verrous atteigne zéro, sauf si le thread est interrompu ou expiré |
void countDown () | Diminuez le nombre verrouillé, si le nombre atteint zéro, réveillez tous les threads en attente bloqués |
long getCount () | Renvoyer le décompte actuel |
Utilisation de CountDownLatch
S'il y a une telle exigence: nous devons analyser les données de plusieurs feuilles dans un Excel. À ce stade, nous pouvons envisager d'utiliser le multi-threading. Chaque thread analyse les données dans une feuille. Une fois toutes les feuilles analysées, le programme doit indiquer que l'analyse est terminée. .
package com.morris.concurrent.tool.countdownlatch.api;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 演示CountDownLatch闭锁的使用
*/
@Slf4j
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
new Thread(()->parse("sheet1"), "t1").start();
new Thread(()->parse("sheet2"), "t2").start();
new Thread(()->parse("sheet3"), "t3").start();
countDownLatch.await();
log.info("parse commplete");
}
private static void parse(String sheet) {
log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
try {
TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
countDownLatch.countDown();
log.info("还有{}个sheet未解析完", countDownLatch.getCount());
}
}
Les résultats sont les suivants:
2020-09-24 13:58:55,551 INFO [t2] (CountDownLatchDemo.java:28) - t2 parse sheet2 begin...
2020-09-24 13:58:55,565 INFO [t1] (CountDownLatchDemo.java:28) - t1 parse sheet1 begin...
2020-09-24 13:58:55,597 INFO [t3] (CountDownLatchDemo.java:28) - t3 parse sheet3 begin...
2020-09-24 13:59:01,565 INFO [t1] (CountDownLatchDemo.java:34) - t1 parse sheet1 end.
2020-09-24 13:59:01,566 INFO [t1] (CountDownLatchDemo.java:36) - 还有2个sheet未解析完
2020-09-24 13:59:02,599 INFO [t3] (CountDownLatchDemo.java:34) - t3 parse sheet3 end.
2020-09-24 13:59:02,599 INFO [t3] (CountDownLatchDemo.java:36) - 还有1个sheet未解析完
2020-09-24 13:59:24,556 INFO [t2] (CountDownLatchDemo.java:34) - t2 parse sheet2 end.
2020-09-24 13:59:24,556 INFO [t2] (CountDownLatchDemo.java:36) - 还有0个sheet未解析完
2020-09-24 13:59:24,556 INFO [main] (CountDownLatchDemo.java:24) - parse commplete
Bien sûr, vous pouvez également utiliser join () pour réaliser:
package com.morris.concurrent.tool.countdownlatch.api;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 使用join完成对excel的解析,与countDownLatch对比
*/
@Slf4j
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> parse("sheet1"), "t1");
t1.start();
Thread t2 = new Thread(() -> parse("sheet2"), "t2");
t2.start();
Thread t3 = new Thread(() -> parse("sheet3"), "t3");
t3.start();
t1.join();
t2.join();
t3.join();
log.info("parse commplete");
}
public static void parse(String sheet) {
log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
try {
TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
}
}
Les résultats sont les suivants:
2020-09-24 14:04:57,732 INFO [t2] (JoinDemo.java:30) - t2 parse sheet2 begin...
2020-09-24 14:04:57,755 INFO [t3] (JoinDemo.java:30) - t3 parse sheet3 begin...
2020-09-24 14:04:57,736 INFO [t1] (JoinDemo.java:30) - t1 parse sheet1 begin...
2020-09-24 14:05:07,757 INFO [t3] (JoinDemo.java:36) - t3 parse sheet3 end.
2020-09-24 14:05:07,758 INFO [t1] (JoinDemo.java:36) - t1 parse sheet1 end.
2020-09-24 14:05:07,757 INFO [t2] (JoinDemo.java:36) - t2 parse sheet2 end.
2020-09-24 14:05:07,759 INFO [main] (JoinDemo.java:26) - parse commplete
Comparaison de CountDownLatch et join:
- CountDownLatch est plus flexible que join. Join doit récupérer l'objet thread avant de pouvoir être utilisé. En général, le pool de threads est utilisé. Les threads du pool de threads ne sont pas exposés à l'extérieur et la jointure ne peut pas être utilisée.
- Join doit attendre que le thread se termine avant de revenir, et CountDownLatch.await () peut revenir une fois que le thread est en cours d'exécution à mi-chemin.
Analyse du code source de CountDownLatch
La couche inférieure de CountDownLatch est basée sur AQS.
Structure de données
java.util.concurrent.CountDownLatch.Sync
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count); // 构造时初始化count的大小
}
int getCount() {
return getState();
}
// await()调用此方法,不为0就会进入同步队列中等待,为0就会直接返回,往下执行
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// countDown()调用此方法,每调用一次state就会-1,当state=0时,会去唤醒同步队列中等待的线程
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
compte à rebours()
java.util.concurrent.CountDownLatch # countDown
public void countDown() {
sync.releaseShared(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer # releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// state-1
doReleaseShared(); // 唤醒同步队列中等待的线程
return true;
}
return false;
}
attendre()
java.util.concurrent.CountDownLatch # await ()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer # AcquérirSharedInterruptiblement
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 判断state是否为0,是就会直接返回
doAcquireSharedInterruptibly(arg); // 进入同步队列中等待,park
}
CountDownLatch personnalisé
package com.morris.concurrent.tool.countdownlatch.my;
import java.util.concurrent.TimeUnit;
/**
* 使用wait-notify实现CountDownLatch
*/
public class WaitNotifyCountDownLatch {
private volatile int count;
public WaitNotifyCountDownLatch(int count) {
this.count = count;
}
public synchronized void countDown() {
if (0 == --count) {
this.notifyAll();
}
}
public synchronized void await() throws InterruptedException {
while (count > 0) {
this.wait();
}
}
public synchronized void await(long timeout, TimeUnit unit) throws InterruptedException {
while (count > 0) {
this.wait(unit.toMillis(timeout));
}
}
public int getCount() {
return count;
}
}