java多執行緒與併發 - 併發工具類
來源:http://blog.csdn.net/sunxianghuang/article/details/52277394
在JDK的併發包裡提供了幾個非常有用的併發工具類。CountDownLatch、CyclicBarrier和Semaphore工具類提供了一種併發流程控制的手段,Exchanger工具類則提供了線上程間交換資料的一種手段。本章會配合一些應用場景來介紹如何使用這些工具類。
等待多執行緒完成的CountDownLatch
CountDownLatch允許一個或多個執行緒等待其他執行緒完成操作。
假如有這樣一個需求:我們需要解析一個Excel裡多個sheet的資料,此時可以考慮使用多執行緒,每個執行緒解析一個sheet裡的資料,等到所有的sheet都解析完之後,程式需要提示解析完成(或者彙總結果)。在這個需求中,要實現主執行緒等待所有執行緒完成sheet的解析操作,最簡單的做法是使用join()方法,如程式碼清單8-1所示。
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public class JoinCountDownLatchTest {
private static Random sr=new Random(47);
private static AtomicInteger result=new AtomicInteger(0);
private static int threadCount=10;
private static class Parser implements Runnable{
String name;
public Parser(String name){
this.name=name;
}
@Override
public void run() {
int sum=0;
int seed=Math.abs(sr.nextInt()) ;
Random r=new Random(47);
for(int i=0;i<100;i++){
sum+=r.nextInt(seed);
}
result.addAndGet(sum);
System.out.println(name+"執行緒的解析結果:"+sum);
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads=new Thread[threadCount];
for(int i=0;i<threadCount;i++){
threads[i]=new Thread(new Parser("Parser-"+i));
}
for(int i=0;i<threadCount;i++){
threads[i].start();
}
for(int i=0;i<threadCount;i++){
threads[i].join();
}
System.out.println("所有執行緒解析結束!");
System.out.println("所有執行緒的解析結果:"+result);
}
}
輸出:
Parser-1執行緒的解析結果:-2013585201
Parser-0執行緒的解析結果:1336321192
Parser-2執行緒的解析結果:908136818
Parser-5執行緒的解析結果:-1675827227
Parser-3執行緒的解析結果:1638121055
Parser-4執行緒的解析結果:1513365118
Parser-6執行緒的解析結果:489607354
Parser-8執行緒的解析結果:1513365118
Parser-7執行緒的解析結果:-1191966831
Parser-9執行緒的解析結果:-912399159
所有執行緒解析結束!
所有執行緒的解析結果:1605138237
join用於讓當前執行執行緒等待join執行緒執行結束。其實現原理是不停檢查join執行緒是否存活,如果join執行緒存活則讓當前執行緒永遠等待。其中,wait(0)表示永遠等待下去,程式碼片段如下。
public class Thread implements Runnable {
......
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {//執行到這裡
while (isAlive()) {
wait(0);//main執行緒永遠等待join執行緒
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
......
}
直到join執行緒中止後,執行緒的this.notifyAll()方法會被呼叫,呼叫notifyAll()方法是在JVM裡實現的,所以在JDK裡看不到,大家可以檢視JVM原始碼。
在JDK 1.5之後的併發包中提供的CountDownLatch也可以實現join的功能,並且比join的功能更多,如程式碼清單8-2所示。
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class CountDownLatchTest {
private static Random sr=new Random(47);
private static AtomicInteger result=new AtomicInteger(0);
private static int threadCount=10;//執行緒數量
private static CountDownLatch countDown=new CountDownLatch(threadCount);//CountDownLatch
private static class Parser implements Runnable{
String name;
public Parser(String name){
this.name=name;
}
@Override
public void run() {
int sum=0;
int seed=Math.abs(sr.nextInt()) ;
Random r=new Random(47);
for(int i=0;i<100;i++){
sum+=r.nextInt(seed);
}
result.addAndGet(sum);
System.out.println(name+"執行緒的解析結果:"+sum);
countDown.countDown();//注意這裡
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads=new Thread[threadCount];
for(int i=0;i<threadCount;i++){
threads[i]=new Thread(new Parser("Parser-"+i));
}
for(int i=0;i<threadCount;i++){
threads[i].start();
}
/*
for(int i=0;i<threadCount;i++){
threads[i].join();
}*/
countDown.await();//將join改為使用CountDownLatch
System.out.println("所有執行緒解析結束!");
System.out.println("所有執行緒的解析結果:"+result);
}
}
輸出:
Parser-0執行緒的解析結果:1336321192
Parser-1執行緒的解析結果:-2013585201
Parser-2執行緒的解析結果:-1675827227
Parser-4執行緒的解析結果:1638121055
Parser-3執行緒的解析結果:908136818
Parser-5執行緒的解析結果:1513365118
Parser-7執行緒的解析結果:489607354
Parser-6執行緒的解析結果:1513365118
Parser-8執行緒的解析結果:-1191966831
Parser-9執行緒的解析結果:-912399159
所有執行緒解析結束!
所有執行緒的解析結果:1605138237
CountDownLatch的建構函式接收一個int型別的引數作為計數器,如果你想等待N個點完成,這裡就傳入N。
當我們呼叫CountDownLatch的countDown方法時,N就會減1,CountDownLatch的await方法會阻塞當前執行緒,直到N變成零。由於countDown方法可以用在任何地方,所以這裡說的N個點,可以是N個執行緒,也可以是1個執行緒裡的N個執行步驟。用在多個執行緒時,只需要把這個CountDownLatch的引用傳遞到執行緒裡即可。
如果有某個解析sheet的執行緒處理得比較慢,我們不可能讓主執行緒一直等待,所以可以使用另外一個帶指定時間的await方法——await(long time,TimeUnit unit),這個方法等待特定時間後,就會不再阻塞當前執行緒。join也有類似的方法。
注意:計數器必須大於等於0,只是等於0時候,計數器就是零,呼叫await方法時不會阻塞當前執行緒。CountDownLatch不可能重新初始化或者修改CountDownLatch物件的內部計數器的值。一個執行緒呼叫countDown方法happen-before,另外一個執行緒呼叫await方法。
public class CountDownLatch {
/**Synchronization control For CountDownLatch. Uses AQS state to represent count.*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);//初始化同步狀態
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
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;
}
}
}
private final Sync sync;//組合一個同步器(AQS)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);//初始化同步狀態
}
/*Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);//
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);//釋放同步狀態
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
同步屏障CyclicBarrier
CyclicBarrier的字面意思是可迴圈使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續執行。
CyclicBarrier預設的構造方法是CyclicBarrier(int parties),其參數列示屏障攔截的執行緒數量,每個執行緒呼叫await方法告訴CyclicBarrier我已經到達了屏障,然後當前執行緒被阻塞。
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
public class CyclicBarrierTest {
private static Random sr=new Random(47);
private static AtomicInteger result=new AtomicInteger(0);
private static int threadCount=10;
//屏障後面執行彙總
private static CyclicBarrier barrier=new CyclicBarrier(threadCount,new Accumulate());
private static class Parser implements Runnable{
String name;
public Parser(String name){
this.name=name;
}
@Override
public void run() {
int sum=0;
int seed=Math.abs(sr.nextInt()) ;
Random r=new Random(47);
for(int i=0;i<(seed%100*100000);i++){
sum+=r.nextInt(seed);
}
result.addAndGet(sum);
System.out.println(System.currentTimeMillis()+"-"+name+"執行緒的解析結果:"+sum);
try {
barrier.await();
System.out.println(System.currentTimeMillis()+"-"+name+"執行緒越過屏障!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class Accumulate implements Runnable{
@Override
public void run() {
System.out.println("所有執行緒解析結束!");
System.out.println("所有執行緒的解析結果:"+result);
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads=new Thread[threadCount];
for(int i=0;i<threadCount;i++){
threads[i]=new Thread(new Parser("Parser-"+i));
}
for(int i=0;i<threadCount;i++){
threads[i].start();
}
}
}
輸出:
1471866228774-Parser-4執行緒的解析結果:631026992
1471866228930-Parser-3執行緒的解析結果:-372785277
1471866228961-Parser-1執行緒的解析結果:-938473891
1471866229008-Parser-7執行緒的解析結果:-396620018
1471866229008-Parser-2執行緒的解析結果:-1159985406
1471866229024-Parser-5執行緒的解析結果:-664234808
1471866229070-Parser-6執行緒的解析結果:556534377
1471866229117-Parser-9執行緒的解析結果:-844558478
1471866229383-Parser-0執行緒的解析結果:919864023
1471866229430-Parser-8執行緒的解析結果:-2104111089
所有執行緒解析結束!
所有執行緒的解析結果:-78376279
1471866229430-Parser-8執行緒越過屏障!
1471866229430-Parser-2執行緒越過屏障!
1471866229430-Parser-9執行緒越過屏障!
1471866229430-Parser-7執行緒越過屏障!
1471866229430-Parser-1執行緒越過屏障!
1471866229430-Parser-3執行緒越過屏障!
1471866229430-Parser-0執行緒越過屏障!
1471866229430-Parser-6執行緒越過屏障!
1471866229430-Parser-4執行緒越過屏障!
1471866229430-Parser-5執行緒越過屏障!
我們發現,各個執行緒解析完成的時間不一致,但是越過屏障的時間卻是一致的。
CyclicBarrier和CountDownLatch的區別
CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset()方法重置。所以CyclicBarrier能處理更為複雜的業務場景。例如,如果計算髮生錯誤,可以重置計數器,並讓執行緒重新執行一次。
CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得Cyclic-Barrier阻塞的執行緒數量。isBroken()方法用來了解阻塞的執行緒是否被中斷。
控制併發執行緒數的Semaphore
Semaphore(訊號量)是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。
多年以來,我都覺得從字面上很難理解Semaphore所表達的含義,只能把它比作是控制流量的紅綠燈。比如××馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入××馬路,但是如果前一百輛中有5輛車已經離開了××馬路,那麼後面就允許有5輛車駛入馬路,這個例子裡說的車就是執行緒,駛入馬路就表示執行緒在執行,離開馬路就表示執行緒執行完成,看見紅燈就表示執行緒被阻塞,不能執行。
應用場景
Semaphore可以用於做流量控制,特別是公用資源有限的應用場景,比如資料庫連線。假如有一個需求,要讀取幾萬個檔案的資料,因為都是IO密集型任務,我們可以啟動幾十個執行緒併發地讀取,但是如果讀到記憶體後,還需要儲存到資料庫中,而資料庫的連線數只有10個,這時我們必須控制只有10個執行緒同時獲取資料庫連線儲存資料,否則會報錯無法獲取資料庫連線。這個時候,就可以使用Semaphore來做流量控制,如程式碼清單8-7所示。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data");
s.release();
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}
在程式碼中,雖然有30個執行緒在執行,但是隻允許10個併發執行。Semaphore的構造方法Semaphore(int permits)接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示允許10個執行緒獲取許可證,也就是最大併發數是10。Semaphore的用法也很簡單,首先執行緒使用Semaphore的acquire()方法獲取一個許可證,使用完之後呼叫release()方法歸還許可證。還可以
用tryAcquire()方法嘗試獲取許可證。
其他方法
Semaphore還提供一些其他方法,具體如下。
int availablePermits():返回此訊號量中當前可用的許可證數。
int getQueueLength():返回正在等待獲取許可證的執行緒數。
boolean hasQueuedThreads():是否有執行緒正在等待獲取許可證。
void reducePermits(int reduction):減少reduction個許可證,是個protected方法。
Collection getQueuedThreads():返回所有等待獲取許可證的執行緒集合,是個protected方法。
執行緒間交換資料的Exchanger
Exchanger(交換者)是一個用於執行緒間協作的工具類。Exchanger用於進行執行緒間的資料交換。它提供一個同步點,在這個同步點,兩個執行緒可以交換彼此的資料。這兩個執行緒通過exchange方法交換資料,如果第一個執行緒先執行exchange()方法,它會一直等待第二個執行緒也執行exchange方法,當兩個執行緒都到達同步點時,這兩個執行緒就可以交換資料,將本執行緒生產出來的資料傳遞給對方。
下面來看一下Exchanger的應用場景。
1、Exchanger可以用於遺傳演算法,遺傳演算法裡需要選出兩個人作為交配物件,這時候會交換兩人的資料,並使用交叉規則得出2個交配結果。
2、Exchanger也可以用於校對工作,比如我們需要將紙製銀行流水通過人工的方式錄入成電子銀行流水,為了避免錯誤,採用AB崗兩人進行錄入,錄入到Excel之後,系統需要載入這兩個Excel,並對兩個Excel資料進行校對,看看是否錄入一致.
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExchangerTest {
private static final Exchanger<String> exgr = new Exchanger<String>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String A = "銀行流水100";// A錄入銀行流水資料
String B=exgr.exchange(A);
System.out.println("A的視角:A和B資料是否一致:" + A.equals(B) +
",A錄入的是:" + A + ",B錄入是:" + B);
} catch (InterruptedException e) {
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String B = "銀行流水200";// B錄入銀行流水資料
String A = exgr.exchange(B);
System.out.println("B的視角:A和B資料是否一致:" + A.equals(B) +
",A錄入的是:" + A + ",B錄入是:" + B);
} catch (InterruptedException e) {
}
}
});
threadPool.shutdown();
}
}
輸出:
B的視角:A和B資料是否一致:false,A錄入的是:銀行流水100,B錄入是:銀行流水200
A的視角:A和B資料是否一致:false,A錄入的是:銀行流水100,B錄入是:銀行流水200
如果兩個執行緒有一個沒有執行exchange()方法,則會一直等待,如果擔心有特殊情況發生,避免一直等待,可以使用exchange(V x,longtimeout,TimeUnit unit)設定最大等待時長。
內容源自:
《Java併發程式設計的藝術》
相關文章
- Java執行緒的併發工具類Java執行緒
- JAVA多執行緒併發Java執行緒
- Java併發指南1:併發基礎與Java多執行緒Java執行緒
- java多執行緒10:併發工具類CountDownLatch、CyclicBarrier和SemaphoreJava執行緒CountDownLatch
- Java多執行緒與併發之ThreadLocalJava執行緒thread
- 深入理解Java多執行緒與併發框(第⑩篇)——併發輔助工具類(很好的玩的工具類)Java執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- 【重學Java】多執行緒進階(執行緒池、原子性、併發工具類)Java執行緒
- 併發工具類(一)等待多執行緒的CountDownLatch執行緒CountDownLatch
- Java高併發與多執行緒(一)-----概念Java執行緒
- 多執行緒系列(十五) -常用併發工具類詳解執行緒
- Java多執行緒併發工具類-訊號量Semaphore物件講解Java執行緒物件
- 併發與多執行緒基礎執行緒
- 多執行緒與併發----Semaphere同步執行緒
- 併發工具類(三)控制併發執行緒的數量 Semphore執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- Java併發-執行緒安全的集合類Java執行緒
- Java併發(一)----程式、執行緒、並行、併發Java執行緒並行
- 多執行緒與高併發(一)多執行緒入門執行緒
- Java併發/多執行緒-CAS原理分析Java執行緒
- JAVA多執行緒和併發基礎Java執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- 多執行緒與併發----讀寫鎖執行緒
- Java多執行緒與併發基礎面試題Java執行緒面試題
- Java併發實戰一:執行緒與執行緒安全Java執行緒
- 【多執行緒與高併發】- 執行緒基礎與狀態執行緒
- Java多執行緒和併發問題集Java執行緒
- 多執行緒併發篇——如何停止執行緒執行緒
- 【多執行緒與高併發】- 淺談volatile執行緒
- 【多執行緒與高併發 2】volatile 篇執行緒
- 多執行緒與併發-----Lock鎖技術執行緒
- Java併發(四)----執行緒執行原理Java執行緒
- Java併發系列 — 執行緒池Java執行緒
- Java併發——執行緒池ThreadPoolExecutorJava執行緒thread
- Go高效併發 10 | Context:多執行緒併發控制神器GoContext執行緒
- Python《多執行緒併發爬蟲》Python執行緒爬蟲
- MySQL多執行緒併發調優MySql執行緒