中級09 - Java多線程初步
介紹多線程帶來的問題,以及基本解決方案。
- 競爭條件帶來的數(shù)據(jù)錯誤問題
- 死鎖的原理、排查與防范
- 線程安全:同步方法、并發(fā)工具包
一 、線程不安全的表現(xiàn) 競爭條件帶來的數(shù)據(jù)錯誤
- i++
- if-then-do
如果最終結(jié)果不正確,那么程序跑地再快也沒有意義。
二、死鎖的產(chǎn)生、排查和防范
1. 產(chǎn)生
Java 中某個鎖只能同時被一個線程持有,當(dāng)兩個線程互相在等待對方持有的鎖時就形成了死鎖。
為了便于理解,想象一手交錢一手交貨的問題:
A 持有現(xiàn)金,B 持有粉,但為了掩人耳目,只能通過一個狹小的窗口進行交易,無法同時一手交錢一手交貨,,倆人都持有對方需要的資源,并且也都在等待對方手上的資源,但誰也不愿先給出自己的資源。
此時便形成了死鎖。
類似的還有 哲學(xué)家就餐問題。
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (lock1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("看不到我");
}
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (lock2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("還是看不到我");
}
}
}
}
}
2. 排查
使用jps
列出當(dāng)前 Java 進程 ID:
jps
17876 Jps
17928 RemoteMavenServer36
9224 Launcher
19260
9164 DeadlockExample
jstack 打印給定 Java 進程中的所有棧信息進行排查:
jstack 9164
2019-09-29 17:01:49
# ...
# 省略
# ...
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000002d8b648 (object 0x00000000d61a4cb0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000002d8a1a8 (object 0x00000000d61a4cc0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.github.hcsp.calculation.DeadlockExample$Thread2.run(DeadlockExample.java:42)
- waiting to lock <0x00000000d61a4cb0> (a java.lang.Object)
- locked <0x00000000d61a4cc0> (a java.lang.Object)
"Thread-0":
at com.github.hcsp.calculation.DeadlockExample$Thread1.run(DeadlockExample.java:24)
- waiting to lock <0x00000000d61a4cc0> (a java.lang.Object)
- locked <0x00000000d61a4cb0> (a java.lang.Object)
Found 1 deadlock.
3. 防范
所有的線程都按照相同的順序獲得資源的鎖。
三、實現(xiàn)線程安全的基本手段
1. 不可變類 Integer/String 等
2. 同步方法
2.1 synchronized
- synchronized (一個對象) 把這個對象當(dāng)成鎖
- static synchronized方法 把 Class 對象當(dāng)成鎖
- 實例的 synchronized方法 把該實例當(dāng)成鎖(等價于 synchronized (this) {})
還是之前多個線程同時修改共享變量時的問題,現(xiàn)在采用 synchronizedd 同步塊,使得同一時刻只能有一個線程持有鎖,去執(zhí)行同步塊中的代碼:
public class Test {
private static int i = 0;
private static final Object lock = new Object(); // 定義一個鎖
public static void main(String[] args) {
for (int j = 0; j < 1000; j++) {
new Thread(Test::modifySharedVariable).start();
}
}
private static void modifySharedVariable() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) { // 使用同步塊
i++;
System.out.println("i = " + i);
}
}
}
除了使用 synchronized 同步塊,還可以使用 synchronized 關(guān)鍵字:
private synchronized static void modifySharedVariable() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
System.out.println("i = " + i);
}
2.2 Collections.synchronized*
如 Collections.synchronizedMap 等,基本只是把原來的 map 相關(guān)方法使用 synchronized 同步塊包裝了一遍。
但是注意,使用 Collections.synchronized* 時,如果存在非集合本身的,未經(jīng) synchronized 的操作,那么并不能保證線程安全,還是需要 synchronized 同步塊。
3. 并發(fā)工具包
3.1 ConcurrentHashMap
下圖中的 Collection 和 Map 都是線程不安全的:
[圖片上傳失敗...(image-96a1e3-1575509314567)]
多個線程并發(fā)訪問 map 是不安全的:
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class Main {
private static final Map<Integer, Integer> map = new HashMap<>();
public static void main(String[] args) {
for (int j = 0; j < 1000; j++) {
new Thread(Main::concurrentlyAccess).start();
}
}
private static void concurrentlyAccess() {
Integer r = new Random().nextInt();
map.put(r, r);
for (Integer i : map.keySet()) {
System.out.println(i);
}
}
}
報錯如下:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$KeyIterator.next(HashMap.java:1469)
at com.github.hcsp.calculation.Main.concurrentlyAccess(Main.java:25)
at java.lang.Thread.run(Thread.java:748)
解決辦法除了使用剛才提到的 synchronized,還可以無腦地使用 JUC 并發(fā)工具包提供的線程安全的 ConcurrentHashMap 替換原本涉及到線程安全問題時的 HashMap。
3.2 Atomic*
可以創(chuàng)建具有原子性操作的數(shù)據(jù),如 AtomicInteger。
3.3 ReentrantLock 可重入鎖
ReentrantLock 通過一個雙向鏈表來實現(xiàn)鎖機制,每個節(jié)點代表一條線程,head 節(jié)點擁有
鎖,其他節(jié)點均被掛起等待喚醒。等到當(dāng)前鎖釋放成功后,會喚醒鏈表中的下一個節(jié)點,并將其更新為新的 head 節(jié)點。
可重入鎖是指:當(dāng)線程請求一個由其它線程持有的鎖時,該線程會阻塞,而當(dāng)線程請求由自己持有的鎖時,如果該鎖是可重入鎖,請求就會成功,否則阻塞。
即當(dāng)一個線程獲取了某個對象鎖后,還可以再次獲得該對象鎖。
ReentrantLock 作為一個可重入鎖,內(nèi)部是通過 state 作為計數(shù)器來統(tǒng)計總共執(zhí)行了多少次 lock 方法,如果同一個鎖 lock 兩次,state 為 2,那么只調(diào)用一次 unlock,state 為 1,鎖尚未釋放,再 unlock 一次,state 為 0,鎖成功釋放。
ReentrantLock 和 synchronized 方法/聲明具有相似的行為和語義,但是更靈活強大:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
private static int i = 0;
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(ReentrantLockTest::concurrentlyAccess).start();
}
}
private static void concurrentlyAccess() {
lock.lock(); // block until condition holds
// 這里可以掩飾 lock.lock(); //
try {
i = i + 1;
System.out.println("i = " + i);
} finally {
lock.unlock();
}
}
}
可重入鎖的作用就是為了避免死鎖,因為 Java 中某個鎖只能同時被一個線程持有,如下例所示,如果 synchronized 鎖不可重入,不可再次獲得,將會造成死鎖,該線程一直等待進入方法 b 需要的這把對象鎖,而該鎖明明又被自己占用著。
所以 synchronized 作為可重入鎖,進入 a 方法時持有當(dāng)前對象鎖,緊接著進入 b 方法時又要請求相同的鎖,此時可重復(fù)持有當(dāng)前對象鎖:
private synchronized void a() {
b();
}
private synchronized void b() {
}
四、線程的狀態(tài)與Object類中的線程方法
1. 線程的狀態(tài)
Java中線程的狀態(tài)分為 6 種:
- 初始(NEW):新創(chuàng)建了一個線程對象,但還沒有調(diào)用start()方法。
- 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運行”。
線程對象創(chuàng)建后,其他線程(比如main線程)調(diào)用了該對象的 start() 方法。該狀態(tài)的線程位于可運行線程池中,等待被線程調(diào)度選中,獲取 CPU 的使用權(quán),此時處于就緒狀態(tài)(ready)。就緒狀態(tài)的線程在獲得 CPU 時間片后變?yōu)檫\行中狀態(tài)(running)。
阻塞(BLOCKED):線程因為需要等待一個鎖時被阻塞的狀態(tài),拿不到鎖,鎖在別的線程手里。
等待(WAITING):前提是線程已經(jīng)擁有鎖了,然后進入該狀態(tài),等待其他線程做出一些特定動作(通知或中斷)
超時等待(TIMED_WAITING):該狀態(tài)不同于 WAITING,它可以在指定的時間后自行返回。
終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢。
2. Obect 類中的線程方法
為什么 Java 中所有的對象都可以作為鎖?因為所有的對象都繼承自 Object 類,而 Object 類提供了線程相關(guān)的方法。
2.1 wait
只有持有某個對象鎖的線程(假設(shè)是 A)才能調(diào)用該對象的 wait() 方法,調(diào)用后 A 會釋放鎖并進入waiting 狀態(tài),直到其他某個線程(B)搶到了對象鎖,調(diào)用了該對象鎖的 notify()/notifyAll() 方法后,被 wait 的 A 線程才能重新獲得鎖的所有權(quán)并繼續(xù)執(zhí)行代碼。
2.2 notify
B 線程調(diào)用 notify() 喚醒一個正在等待該鎖的線程 A,若有多個線程都在等待,那么也只會根據(jù) JVM 內(nèi)部機制喚醒其中一個。
2.3 notifyAll
notifyAll() 喚醒所有正在等待該對象鎖的線程。
notify()/notifyAll() 方法所在的 synchronized 方法或塊結(jié)束后,線程 B 釋放該對象鎖,然后這些被喚醒的線程會和其他線程一起按照常規(guī)自由競爭該鎖。
五、生產(chǎn)者和消費者模型(多線程經(jīng)典問題)
每當(dāng)條件滿足時,一個線程被喚醒,做完事情后,讓自己繼續(xù)沉睡的同時順便喚醒別的線程。
請實現(xiàn)一個生產(chǎn)者/消費者模型,其中:
生產(chǎn)者生產(chǎn)10個隨機的整數(shù)供消費者使用(隨機數(shù)可以通過new Random().nextInt()獲得)
使得標(biāo)準(zhǔn)輸出依次輸出它們,例如:
Producing 42
Consuming 42
Producing -1
Consuming -1
...
Producing 10086
Consuming 10086
Producing -12345678
Consuming -12345678
下面演示三種實現(xiàn)方式:
- wait/notify/notifyAll
- Lock/Condition
- BlockingQueue
1. wait/notify/notifyAll
使用最基本的 Object 類中的線程方法和 synchronized。
Boss,整體調(diào)度:
package com.github.hcsp.multithread;
public class Boss {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Container container = new Container();
Producer producer = new Producer(container, lock);
Consumer consumer = new Consumer(container, lock);
producer.start();
consumer.start();
producer.join();
producer.join();
}
}
Container,盛放制造品的容器:
package com.github.hcsp.multithread;
import java.util.Optional;
public class Container {
private Optional<Integer> value = Optional.empty();
public Optional<Integer> getValue() {
return value;
}
public void setValue(Optional<Integer> value) {
this.value = value;
}
}
Producerr,生產(chǎn)者:
package com.github.hcsp.multithread;
import java.util.Optional;
import java.util.Random;
public class Producer extends Thread {
private Container container;
private Object lock;
public Producer(Container container, Object lock) {
this.container = container;
this.lock = lock;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (container.getValue().isPresent()) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Integer r = new Random().nextInt();
container.setValue(Optional.of(r));
System.out.println("Producing " + r);
lock.notify();
}
}
}
}
Consumer,消費者:
package com.github.hcsp.multithread;
import java.util.Optional;
public class Consumer extends Thread {
private Object lock;
private Container container;
public Consumer(Container container, Object lock) {
this.container = container;
this.lock = lock;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (!container.getValue().isPresent()) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Integer v = container.getValue().get();
container.setValue(Optional.empty());
System.out.println("Consuming " + v);
lock.notify();
}
}
}
}
2. Lock/Condition
使用 ReentrantLock 和 Condition。
ReentrantLock 比 synchronized 自動加解鎖機制更靈活。
Condition 是個接口,在 Java1.5 中出現(xiàn),用來替代傳統(tǒng)的 Object Monitor Methods 中 wait/notify/notifyAll() 的 是 await/signal/signalAll(),這種方式使線程間協(xié)作更加安全和高效,推薦使用。
Condition 依賴于 Lock 接口,生成一個 Condition 的基本代碼是 lock.newCondition()。
Condition 抽離出了線程調(diào)用機制,可以和任意的鎖結(jié)合在一起,使得同一個對象可以有多個等待隊列(一個鎖可以 newCondition() 多次)。
使得在下面的例子中,當(dāng)容器滿了或者空了時,同一時間內(nèi)只需要通知一條對應(yīng)的等待中的線程。
Boss2:
package com.github.hcsp.multithread;
import java.util.concurrent.locks.ReentrantLock;
public class Boss2 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Container2 container = new Container2(lock);
Producer2 producer = new Producer2(container, lock);
Consumer2 consumer = new Consumer2(container, lock);
consumer.start();
producer.start();
producer.join();
producer.join();
}
}
Container2:
package com.github.hcsp.multithread;
import java.util.Optional;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Container2 {
private Condition notConsumedYet; // 尚未被消費掉
private Condition notProducedYet; // 尚未被生產(chǎn)出來
private Optional<Integer> value = Optional.empty();
public Container2(ReentrantLock lock) {
this.notConsumedYet = lock.newCondition();
this.notProducedYet = lock.newCondition();
}
public Condition getNotConsumedYet() {
return notConsumedYet;
}
public Condition getNotProducedYet() {
return notProducedYet;
}
public Optional<Integer> getValue() {
return value;
}
public void setValue(Optional<Integer> value) {
this.value = value;
}
}
Producer2:
package com.github.hcsp.multithread;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
public class Producer2 extends Thread {
private Container2 container;
private ReentrantLock lock;
public Producer2(Container2 container, ReentrantLock lock) {
this.container = container;
this.lock = lock;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
while (container.getValue().isPresent()) {
try {
container.getNotProducedYet().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Integer r = new Random().nextInt();
container.setValue(Optional.of(r));
System.out.println("Producing " + r);
container.getNotConsumedYet().signal();
} finally {
lock.unlock();
}
}
}
}
Consumer2:
package com.github.hcsp.multithread;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
public class Consumer2 extends Thread {
private Container2 container;
private ReentrantLock lock;
public Consumer2(Container2 container, ReentrantLock lock) {
this.container = container;
this.lock = lock;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
while (!container.getValue().isPresent()) {
try {
container.getNotConsumedYet().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Integer v = container.getValue().get();
container.setValue(Optional.empty());
System.out.println("Consuming " + v);
container.getNotProducedYet().signal();
} finally {
lock.unlock();
}
}
}
}
另外說一下個人對以上這 2 種生產(chǎn)者和消費者模型實現(xiàn)方式的理解:
設(shè)置條件判斷是因為需要先生產(chǎn)后才能消費, 未消費前也不能再生產(chǎn), 所以當(dāng)生產(chǎn)或消費條件不滿足時, 需要令當(dāng)前線程進入 waiting。
另一方面,是為了當(dāng)前線程被中斷和假喚醒時,要繼續(xù)進入 waiting, 并且要用循環(huán)一直"盯"著。
另外,在 for 循環(huán)中,synchronized 和 ReentrantLock 作為可重入鎖可以重復(fù)加鎖, 條件不成熟時也可重新進入鎖,故也需要條件判斷。
3. BlockingQueue
BlockingQueue 取回元素時要等待隊列非空,存儲元素時要等待隊列非滿,可以方便的實現(xiàn)生產(chǎn)者和消費者模型,創(chuàng)建兩條 BlockingQueue,一條負(fù)責(zé)管理產(chǎn)品,一條負(fù)責(zé)管理調(diào)度信號,不再需要手動創(chuàng)建容器。
Boss3:
package com.github.hcsp.multithread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Boss3 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue queue = new ArrayBlockingQueue(1);
BlockingQueue signalQueue = new ArrayBlockingQueue(1);
Producer3 producer = new Producer3(queue, signalQueue);
Consumer3 consumer = new Consumer3(queue, signalQueue);
producer.start();
consumer.start();
producer.join();
producer.join();
}
}
Producer3:
package com.github.hcsp.multithread;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
public class Producer3 extends Thread {
BlockingQueue<Integer> queue;
BlockingQueue<Integer> signalQueue;
public Producer3(BlockingQueue<Integer> queue, BlockingQueue<Integer> signalQueue) {
this.queue = queue;
this.signalQueue = signalQueue;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int r = new Random().nextInt();
System.out.println("Producing " + r);
try {
queue.put(r);
signalQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Consumer3:
package com.github.hcsp.multithread;
import java.util.concurrent.BlockingQueue;
public class Consumer3 extends Thread {
BlockingQueue<Integer> queue;
BlockingQueue<Integer> signalQueue;
public Consumer3(BlockingQueue<Integer> queue, BlockingQueue<Integer> signalQueue) {
this.queue = queue;
this.signalQueue = signalQueue;
}
@Override
public void run () {
for (int i = 0; i < 10; i++) {
try {
System.out.println("Consuming " + queue.take());
signalQueue.put(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
六、線程池與 Callable/Future
1. Callable/Future
1.1 Callable
Callable 接口引入自 Java1.5,和 Runnable 一樣,它們的實例設(shè)計用于在其他線程中執(zhí)行,但不同的是, Callable 可以返回值,也能拋異常。
1.2 Future
Future 接口代表?個“未來才會返回的結(jié)果”,調(diào)用 get() 方法會等待直到拿到計算結(jié)果。
2. 什么是線程池
2.1 定義
Java 的線程調(diào)度完全依賴于操作系統(tǒng)的線程調(diào)度,線程是昂貴的,不能無節(jié)制地開辟線程,以免將操作系統(tǒng)的資源耗盡,所以線程池是預(yù)先定義好的若干個線程。
2.2 Java 中的線程池
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 簡單場景下為方便起見,直接使用 Executors 工具類快速創(chuàng)建線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// submit 方法會立刻返回一個 Future(類似于 JS 中的 Promise)
// 提交的任務(wù)會異步執(zhí)行,不會阻塞當(dāng)前線程
Future<Integer> future1 = threadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return 0;
}
});
Future<String> future2 = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "ojbk";
}
});
Future<Object> future3 = threadPool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
throw new RuntimeException();
}
});
System.out.println(future1.get());
System.out.println(future2.get());
System.out.println(future3.get());
}
2.3 線程池的構(gòu)造函數(shù)(有坑待填)
3. 實戰(zhàn):多線程的 WordCount
創(chuàng)建 WordCount 對象時傳入文件列表,可統(tǒng)計這些文件中的單詞數(shù)。
使用方法:
List<File> files = xxx;
WordCount wordCount = new WordCount(10);
Map<String, Integer> countResult = wordCount.count(files);
System.out.println(countResult);
WordCount 類的實現(xiàn):
package com.github.hcsp.multithread;
import org.apache.commons.io.FileUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class WordCount {
private final int threadNum;
private ExecutorService threadPool;
public WordCount(int threadNum) {
threadPool = Executors.newFixedThreadPool(threadNum);
this.threadNum = threadNum;
}
/**
* 統(tǒng)計文件中各單詞的數(shù)量
*
* @param files 文件列表
* @return 單詞統(tǒng)計結(jié)果
* @throws IOException 文件讀寫出錯時拋出
* @throws ExecutionException Future 取回結(jié)果出錯時拋出
* @throws InterruptedException 線程被中斷時拋出
*/
public Map<String, Integer> count(List<File> files) throws IOException, ExecutionException, InterruptedException {
BufferedReader reader = new BufferedReader(new FileReader(mergeFilesIntoSingleFile(files)));
List<Future<Map<String, Integer>>> futures = new ArrayList<>();
// 開辟若干個線程,每個線程讀取文件的一行內(nèi)容,并將單詞統(tǒng)計結(jié)果返回
// 最后主線程將工作線程返回的結(jié)果匯總在一起
for (int i = 0; i < threadNum; i++) {
futures.add(threadPool.submit(new WorkerJob(reader)));
}
// 最終結(jié)果集
Map<String, Integer> finalResult = new HashMap<>();
// 將futures中的每個子結(jié)果集合并到終集中
for (Future<Map<String, Integer>> future : futures) {
Map<String, Integer> resultFromWorker = future.get();
mergeWorkerResultIntoFinalResult(resultFromWorker, finalResult);
}
threadPool.shutdown();
return finalResult;
}
/**
* 將文件列表中的文件合并為一個文件
*
* @param files 文件列表
* @return 結(jié)果文件
* @throws IOException 文件讀寫出錯時拋出
*/
private File mergeFilesIntoSingleFile(List<File> files) throws IOException {
File result = File.createTempFile("tmp", "");
for (File file : files) {
String encoding = "UTF-8";
FileUtils.write(result, FileUtils.readFileToString(file, encoding), encoding, true);
}
return result;
}
/**
* 將子集合并到終集中
*
* @param resultFromWorker 終集
* @param finalResult 子集
*/
private void mergeWorkerResultIntoFinalResult(Map<String, Integer> resultFromWorker,
Map<String, Integer> finalResult) {
for (Map.Entry<String, Integer> entry : resultFromWorker.entrySet()) {
String word = entry.getKey();
int mergedResult = finalResult.getOrDefault(word, 0) + entry.getValue();
finalResult.put(word, mergedResult);
}
}
static class WorkerJob implements Callable<Map<String, Integer>> {
private BufferedReader reader;
private WorkerJob(BufferedReader reader) {
this.reader = reader;
}
@Override
public Map<String, Integer> call() throws Exception {
String line;
Map<String, Integer> result = new HashMap<>();
while ((line = reader.readLine()) != null) {
String[] words = line.split(" ");
// System.out.println(Thread.currentThread().getName());
// System.out.println(line);
// System.out.println();
for (String word : words) {
result.put(word, result.getOrDefault(word, 0) + 1);
}
}
return result;
}
}
}
簡單刪除一些代碼就可以實現(xiàn)單文件統(tǒng)計(略)。
參考: