CountDownLunch(閉鎖)、CyclicBarrier(柵欄鎖)、Semaphore(信號量)的區(qū)別
共 7703字,需瀏覽 16分鐘
·
2024-05-31 22:02
CountDownLunch
countDownLunch,又叫閉鎖。它有三個(gè)關(guān)鍵的api:
-
new CountDownLatch(count); 創(chuàng)建一個(gè)閉鎖,并聲明count的值 -
countDownLatch.await();如果countDownLunch的count不是0,則阻塞當(dāng)前線程直到count等0 -
countDownLatch.countDown();將countDownLunch中的count減一
代碼樣例:
Logger logger = LoggerFactory.getLogger(this.getClass());
//創(chuàng)建一個(gè)count=1的閉鎖
CountDownLatch countDownLatch = new CountDownLatch(1);
List<Thread> threads = new ArrayList<>();
//創(chuàng)建5個(gè)線程
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
logger.info("[{}]在等待發(fā)令槍", Thread.currentThread().getName());
try {
//等待閉鎖的count=0
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("槍響了,[{}]跑!", Thread.currentThread().getName());
}, "t" + (i + 1));
thread.start();
threads.add(thread);
try {
//讓出CPU
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.info("開槍,開跑!");
//將count--
countDownLatch.countDown();
try {
//讓出CPU
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//循環(huán)等待所有線程結(jié)束
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
運(yùn)行結(jié)果:
從結(jié)果中可以看出來,t1~t5是同時(shí)開跑的。需要注意的是,countDownLatch.await();會在count的值等于0時(shí),喚醒被阻塞的線程,但是被喚醒的線程是否馬上就可執(zhí)行,這個(gè)要看CPU的調(diào)度,不一定被喚醒后,馬上就可以執(zhí)行。
上面是多等一的用法,下面來一個(gè)一等多的用法:
public static void main(String[] args) {
int count = 5;
CountDownLatch countDownLatch = new CountDownLatch(count);
String[] list = new String[count];
Random random = new Random();
for (int i = 0; i < count; i++) {
int finalI = i;
Thread thread = new Thread(() -> {
for (int j = 0; j <= 100; j++) {
try {
TimeUnit.MILLISECONDS.sleep(random.nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
list[finalI]= Thread.currentThread().getName()+"("+j+"%)";
System.out.print("\r"+ Arrays.toString(list));
}
countDownLatch.countDown();
}, "t" + (i + 1));
thread.start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("\n結(jié)束了");
}
結(jié)果如下:
與join相比相同點(diǎn):
都可以在某種程度上等待線程執(zhí)行完畢
與join相比不同點(diǎn):
-
join是Thread的方法,需要持有Thread的引用,但是現(xiàn)在很多時(shí)候都是像線程池中提交任務(wù)的,很難拿到這個(gè)Thread的引用。但是CountDownLunch是可以作為全局變量的,這個(gè)引用比較好拿到。
-
join是一定要等待線程結(jié)束的,但是CountDownLunch還是比較靈活的,可以在任意時(shí)刻countDown。
CyclicBarrier
CyclicBarrier,又叫柵欄鎖
Logger logger = LoggerFactory.getLogger(this.getClass());
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, ()->{
logger.info("cyclicBarrier被置為0了,{}",Thread.currentThread().getName());
});
logger.info("cyclicBarrier初始化為2,{}",Thread.currentThread().getName());
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
logger.info("cyclicBarrier -1,[{}]",Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
logger.info("cyclicBarrier 0了,[{}]",Thread.currentThread().getName());
}, "t" + (i + 1));
thread.start();
threads.add(thread);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//循環(huán)等待所有線程結(jié)束
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
執(zhí)行結(jié)果:
從執(zhí)行結(jié)果可以看出來,cyclicBarrier是可以循環(huán)使用的,當(dāng)cyclicBarrier的值=0時(shí),會調(diào)用CyclicBarrier構(gòu)造器中的runnable同時(shí)會將count重置為一開始設(shè)定的值。與countDownLunch相比:
相同點(diǎn):
在count不等于0時(shí),調(diào)用await的線程也是會阻塞的。
不同點(diǎn):
-
cyclicBarrier可以循環(huán)使用,countDownLunch是一次性的
-
cyclicBarrier只要調(diào)用await就會使count-1,但是countDownLunch需要手動調(diào)用countDown方法
Semaphore
Semaphore,又叫信號量
信號量是用來限制一個(gè)時(shí)間點(diǎn)下,使用某資源的最大線程數(shù)。信號量限制的是線程數(shù),而不是資源數(shù)。
Semaphore semaphore = new Semaphore(3);
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("初始化了一個(gè)大小為3的信號量。");
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
logger.info("[{}]嘗試申請一個(gè)資源",Thread.currentThread().getName());
try {
semaphore.acquire();
logger.info("[{}]申請到了資源",Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("[{}]釋放了一個(gè)資源",Thread.currentThread().getName());
semaphore.release();
}, "t" + (i + 1));
thread.start();
threads.add(thread);
try {
//讓出CPU
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//循環(huán)等待所有線程結(jié)束
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
執(zhí)行結(jié)果:
在上面的代碼中,首先創(chuàng)建了一個(gè)大小為3的信號量,然后創(chuàng)建了5個(gè)線程去依次申請資源。從日志中可以看出,線程123都順利的拿到了資源,但是線程45在申請資源時(shí)發(fā)生了阻塞,當(dāng)t1釋放資源時(shí),t4先獲取到資源,t2釋放資源時(shí),t5獲取到了資源。
