From:Java并發編程的藝術
- 目錄
BiBi - 并發編程 -0- 開篇
BiBi - 并發編程 -1- 挑戰
BiBi - 并發編程 -2- volatile
BiBi - 并發編程 -3- 鎖
BiBi - 并發編程 -4- 原子操作
BiBi - 并發編程 -5- Java內存模型
BiBi - 并發編程 -6- final關鍵字
BiBi - 并發編程 -7- DCL
BiBi - 并發編程 -8- 線程
BiBi - 并發編程 -9- ReentrantLock
BiBi - 并發編程 -10- 隊列同步器
BiBi - 并發編程 -11- 并發容器
BiBi - 并發編程 -12- Fork/Join框架
BiBi - 并發編程 -13- 并發工具類
BiBi - 并發編程 -14- 線程池
BiBi - 并發編程 -15- Executor框架
1. CountDownLatch【等待其它線程完成】
應用場景:主線程等待其他線程執行完后再繼續執行。
使用join實現
thread1.start();
thread2.start();
thread1.join(); //或者thread1.join(5000)
thread2.join();
println("執行完成");
join方法的原理:不停地檢查join線程是否存活,如果存活則繼續等待,直到join線程終止后,調用notifyAll()方法。
while (isAlive()) {
wait(0); //表示永遠等待下去
}
CountDownLatch實現
package com.ljg;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
static CountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) {
new Thread(new ThreadTest(c)).start();
new Thread(new ThreadTest(c)).start();
try {
c.await(); //或者c.await(5000)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行完成");
}
public static class ThreadTest implements Runnable {
CountDownLatch c;
public ThreadTest(CountDownLatch c) {
this.c = c;
}
@Override
public void run() {
//處理邏輯
c.countDown();
}
}
}
CountDownLatch要比join方法使用起來更加靈活。【體會】
注意:CountDownLatch不能重新初始化或修改其內部的計數器,即new CountDownLatch(2)中的2,不能被修改。并且這里的計數2,可以是2個線程,也可以是一個線程里的2個執行步驟。
2. CyclicBarrier【同步屏障】
應用場景:多線程計算數據,最后合并計算結果
字面含義:循環使用的屏障。它能讓一個線程到達一個屏障【也可以叫同步點】時被阻塞,直到最后一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續執行。
//在所有線程到達屏障后,執行barrierAction任務。
public CyclicBarrier(int parties, Runnable barrierAction) { }
例子:統計Excel中每個sheet表中的用戶流水,最后再用barrierAction處理計算結果。
package com.ljg;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class BankWaterService implements Runnable {
//創建4個屏障,并點定義所有屏障到達后的處理任務
private CyclicBarrier c = new CyclicBarrier(4, this);
//啟動4個線程
private Executor executor = Executors.newFixedThreadPool(4);
private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new ConcurrentHashMap<>();
private void count() {
for (int i = 0; i < 4; ++i) {
executor.execute(new Runnable() {
@Override
public void run() {
//記錄每個線程的計算結果
sheetBankWaterCount.put(Thread.currentThread().getName(), 177);
try {
//插入一個屏障
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
@Override
public void run() {
int result = 0;
for (Map.Entry<String, Integer> sheet : sheetBankWaterCount.entrySet()) {
result += sheet.getValue();
}
System.out.println("計算結果為:" + result);
}
public static void main(String[] args) {
BankWaterService bankWaterService = new BankWaterService();
bankWaterService.count();
}
}
CyclicBarrier與CountDownLatch的區別
CountDownLatch的計數器一旦初始化就不能再改動,而CyclicBarrier的計數器可以通過reset()方法重置。如:當計算結果發生錯誤,可以重置計數器,并讓線程重新執行一次。因此CyclicBarrier能夠處理更加復雜的業務場景。
CyclicBarrier可以通過isBroken()判斷線程是否被中斷。
3. Semaphore【控制并發線程數】
應用場景:用來做流量控制,特別是公共資源有限制的場景,如:數據庫連接。跟自定義同步組件TwinsLock功能類似。
例子:允許只有10個并發線程執行。
package com.ljg;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static final int THREAD_COUNT = 50;
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();
/*
*操作數據
*/
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}
上述代碼中啟動了50個線程,但只允許10個線程并發執行,其它線程處于等待狀態。
4. Exchanger【線程間交換數據】
一個線程先執行exchange()方法,他會一直等待第二個線程也執行exchange()方法,然后就可以交換數據。
package com.ljg;
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<>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
String A = "銀行流水A";
try {
exgr.exchange(A); //也可以設置最大等待時長
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
String B = "銀行流水B";
try {
String back = exgr.exchange(B);
System.out.println("從其他線程獲取的數據為:" + back);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}