多線程常見的四種同步工具類有:Semaphore信號量、CountDownLatch 閉鎖、CyclicBarrier 柵欄、Exchanger 交換。
1. Semaphore 信號量
Semaphore 信號量,通過維護自身線程個數(shù),并提供同步機制。使semaphore可以控制同時訪問資源的線程個數(shù)??梢詫崿F(xiàn)互斥鎖的功能
與互斥鎖的區(qū)別,互斥鎖別的線程在拿到資源需要自己釋放才能讓其他線程獲取資源,而semaphore對象是可以讓另一個對象來釋放鎖,可以用于對死鎖的恢復(fù)。
例子如下:
package ThreadPractice;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3); // 3棧信號燈
for(int i=0;i<10;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
sp.acquire(); // 要獲取燈
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() +
"進入,當前已有" + (3-sp.availablePermits()) + "個并發(fā)"); // 獲得信號
try {
Thread.sleep((long)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() +
"即將離開");
sp.release(); // 釋放燈
System.out.println("線程" + Thread.currentThread().getName() +
"已離開,當前已有" + (3-sp.availablePermits()) + "個并發(fā)");
}
};
service.execute(runnable);
}
}
}
=======console=======
線程pool-1-thread-1進入,當前已有1個并發(fā)
線程pool-1-thread-2進入,當前已有2個并發(fā)
線程pool-1-thread-3進入,當前已有3個并發(fā)
線程pool-1-thread-2即將離開
線程pool-1-thread-2已離開,當前已有2個并發(fā)
線程pool-1-thread-4進入,當前已有3個并發(fā)
線程pool-1-thread-3即將離開
線程pool-1-thread-3已離開,當前已有2個并發(fā)
線程pool-1-thread-5進入,當前已有3個并發(fā)
線程pool-1-thread-4即將離開
每一個acquire()方法表示獲取一個信號,當型號量等于規(guī)定值是則等待直到有信號被release()釋放。也就是說semaphore是可以獲取與釋放值的。
semaphore的感覺有點像線程池,不過線程池是用來管理線程提高效率且實際工作線程是由線程池來創(chuàng)建的,而信號燈主要用來限制管理資源且需要自己手動創(chuàng)建線程,當信號燈不夠時則被刮起,當有一個釋放后就重新喚醒等待隊列中的線程。這個等待隊列內(nèi)部又是基于AQS共享模式建立。
semaphore比如操場上有5個跑道,一個跑道一次只能有一個學生在上面跑步,一旦所有跑道在使用,那么后面的學生就需要等待,直到有一個學生不跑了
Semaphore與ReentrantLock一樣,既可以實現(xiàn)公平鎖也可以實現(xiàn)非公平鎖。公平鎖就是調(diào)用acquire獲取信號的順序遵循FIFO隊列;而非公平鎖則是搶占式的,通過搶占CPU資源實現(xiàn)。
2. CountDownLatch 閉鎖
CountDownLatch 閉鎖,內(nèi)置計數(shù)器,每個線程計數(shù),當線程都歸零時,主線程才繼續(xù)運行。即通過調(diào)用CountDownLatch對象的countDown方法就將計數(shù)器減1,當計數(shù)到達0時,則所有等待者或單個等待者開始執(zhí)行。
package ThreadPractice;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountdownLatchTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(3);
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
System.out.println("線程" + Thread.currentThread().getName() +
"正準備接受命令");
cdOrder.await(); // 等待歸零后就繼續(xù)運行
System.out.println("線程" + Thread.currentThread().getName() +
"已接受命令");
Thread.sleep((long)(Math.random()*10000));
System.out.println("線程" + Thread.currentThread().getName() +
"回應(yīng)命令處理結(jié)果");
cdAnswer.countDown(); // 將計數(shù)減1
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("線程" + Thread.currentThread().getName() +
"即將發(fā)布命令");
cdOrder.countDown();
System.out.println("線程" + Thread.currentThread().getName() +
"已發(fā)送命令,正在等待結(jié)果");
cdAnswer.await();
System.out.println("線程" + Thread.currentThread().getName() +
"已收到所有響應(yīng)結(jié)果");
} catch (Exception e) {
e.printStackTrace();
}
service.shutdown();
}
}
======console======
線程pool-1-thread-1正準備接受命令
線程pool-1-thread-3正準備接受命令
線程pool-1-thread-2正準備接受命令
線程main即將發(fā)布命令
線程main已發(fā)送命令,正在等待結(jié)果
線程pool-1-thread-1已接受命令
線程pool-1-thread-2已接受命令
線程pool-1-thread-3已接受命令
線程pool-1-thread-2回應(yīng)命令處理結(jié)果
線程pool-1-thread-1回應(yīng)命令處理結(jié)果
線程pool-1-thread-3回應(yīng)命令處理結(jié)果
線程main已收到所有響應(yīng)結(jié)果
3. CyclicBarrier 柵欄
CyclicBarrier 柵欄,cyclic會等待其他線程,此時當前線程會阻塞,計算阻塞的個數(shù),當阻塞線程到了規(guī)定值之后,再繼續(xù)放行。
package ThreadPractice;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3); // 約定3個等待
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("線程" + Thread.currentThread().getName() +
"即將到達集合地點1,當前已有" + (cb.getNumberWaiting()+1) + "?個已經(jīng)到達" + (cb.getNumberWaiting()==2?"都到齊了,繼續(xù)走":"正在等候"));
cb.await(); // 開始等待
Thread.sleep((long)(Math.random()*10000));
System.out.println("線程" + Thread.currentThread().getName() +
"即將到達集合地點2,當前已有" + (cb.getNumberWaiting()+1) + "?個已經(jīng)到達" + (cb.getNumberWaiting()==2?"都到齊了,繼續(xù)走":"正在等候"));
cb.await();
Thread.sleep((long)(Math.random()*10000));
System.out.println("線程" + Thread.currentThread().getName() +
"即將到達集合地點3,當前已有" + (cb.getNumberWaiting() + 1) + "?個已經(jīng)到達" + (cb.getNumberWaiting()==2?"都到齊了,繼續(xù)走":"正在等候"));
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}
=====console======
線程pool-1-thread-2即將到達集合地點1,當前已有1?個已經(jīng)到達正在等候
線程pool-1-thread-3即將到達集合地點1,當前已有2?個已經(jīng)到達正在等候
線程pool-1-thread-1即將到達集合地點1,當前已有3?個已經(jīng)到達都到齊了,繼續(xù)走
線程pool-1-thread-1即將到達集合地點2,當前已有1?個已經(jīng)到達正在等候
線程pool-1-thread-2即將到達集合地點2,當前已有2?個已經(jīng)到達正在等候
問:上面講的Semaphore與CyclicBarrier比較?
答:感覺semaphore與cyclicbarrier剛好相反,前者是一開始運行并獲取信號,直到信號到達規(guī)定值后就阻塞,通過release()后再繼續(xù),而cyclicbarrier則是一開始await()就開始阻塞,直到到達規(guī)定值后柵欄才放行運行,兩個剛好相反。
問:和之前講的CyclicBarrier柵欄,也是等到大家到了一起出發(fā),兩者都是讓線程等待其他線程,這與CountDownLatch有什么區(qū)別呢?
答:總體來說的區(qū)別:
1.CountDownLatch采用計數(shù)器減1,而CyclicBarrier柵欄則是加1到規(guī)定值后一起放行。
2.CountDownLatch通過調(diào)用await()阻塞通過countdown()減1,當歸零的時候就恢復(fù),這兩個方法是分開的,也就是說調(diào)用countdown()減1之后線程并不會阻塞,而是接著執(zhí)行任務(wù),而CyclicBarrier只能通過await()開始阻塞直到阻塞到一定程度柵欄才開始放行。
3.CountDownLatch計數(shù)歸零后就歸零了,無法重置,故也就無法重復(fù)使用。而CyclicBarrier通過柵欄機制,只計算當前阻塞的個數(shù)是否到達目標值,未達到阻塞,滿了則放行,是對值的比較,故是可以重復(fù)使用的。
4. Exchanger 交換
Exchanger 交換,實現(xiàn)兩個線程之間的數(shù)據(jù)交換。一個線程等待另一個都到達了交換點就進行數(shù)據(jù)的交換。
package ThreadPractice;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExchangerTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
service.execute(new Runnable(){
public void run() {
try {
String data1 = "蘋果";
System.out.println("線程" + Thread.currentThread().getName() +
"正在把數(shù)據(jù)" + data1 +"換出去");
Thread.sleep((long)(Math.random()*10000)); // 休息時間不一樣
String data2 = (String)exchanger.exchange(data1);
System.out.println("線程" + Thread.currentThread().getName() +
"換回的數(shù)據(jù)為" + data2);
}catch(Exception e){
}
}
});
service.execute(new Runnable(){
public void run() {
try {
String data1 = "錢";
System.out.println("線程" + Thread.currentThread().getName() +
"正在把數(shù)據(jù)" + data1 +"換出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("線程" + Thread.currentThread().getName() +
"換回的數(shù)據(jù)為" + data2);
}catch(Exception e){
}
}
});
}
}
======console======
線程pool-1-thread-1正在把數(shù)據(jù)蘋果換出去
線程pool-1-thread-2正在把數(shù)據(jù)錢換出去
線程pool-1-thread-1換回的數(shù)據(jù)為錢
線程pool-1-thread-2換回的數(shù)據(jù)為蘋果