更多精彩請(qǐng)關(guān)注公眾號(hào)xhJaver,京東java工程師和你一起成長(zhǎng)
上一篇我們講了講線程池的基本概念以及幾種常見的線程池匾乓,今天我們來趁熱打鐵模擬下在項(xiàng)目中怎么用這線程池
一、線程池實(shí)戰(zhàn)例子
項(xiàng)目背景:需要查出一百個(gè)用戶的信息恬涧,并且給他們的郵箱發(fā)送郵件,打印出最終結(jié)果
用戶類
public class User {
private Integer id;
private String email;
public User(Integer id, String email) {
this.id =id;
this.email =email;
}
public String getEmail() {
return email;
}
}
任務(wù)類
public class Task implements Callable<String> {
private Integer id;
public Task(Integer id) {
this.id = id;
}
@Override
public String call() throws Exception {
//調(diào)用業(yè)務(wù)方提供的查user的服務(wù)奏夫,id不同薛匪,創(chuàng)建任務(wù)的時(shí)候就傳過來id
User user = DoSomethingService.queryUser(this.id);
//調(diào)用業(yè)務(wù)方提供發(fā)送郵件的服務(wù)月匣,email不同
String result = DoSomethingService.sendUserEmail(user.getEmail());
return result;
}
}
提供的服務(wù)類
//業(yè)務(wù)提供的服務(wù)
public class DoSomethingService {
//查詢用戶100ms
public static User queryUser(Integer id) throws InterruptedException {
//這里可以調(diào)用查詢user的sql語句
Thread.sleep(100);
User u= new User(id,id+"xhJaver.com");
return u;
}
//發(fā)送郵件50ms
public static String sendUserEmail(String email) throws InterruptedException {
if (email!=null){
//這里可以調(diào)用發(fā)送email的語句
Thread.sleep(50);
return "發(fā)送成功"+email;
}else {
return "發(fā)送失敗";
}
}
}
我們?cè)賮肀容^一下單線程情況下和多線程情況下相同的操作差別有多大
public class SingleVSConcurrent {
public static void main(String[] args) {
//我們模擬一百個(gè)用戶匈睁,我們查出來這一百個(gè)用戶然后再給他們發(fā)郵件
long singleStart = System.currentTimeMillis();
for (int i=0;i<100;i++){
User user = null;
try {
user = DoSomethingService.queryUser(i);
String s = DoSomethingService.sendUserEmail(user.getEmail());
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long singleEnd = System.currentTimeMillis();
System.out.println("單線程共用了"+(singleEnd-singleStart)+"ms");
System.out.println("-------分割線-----------------分割線-----------------分割線-----------------分割線-----------------分割線----------");
long concurrentStart = System.currentTimeMillis();
//構(gòu)建要做的任務(wù)列表,查詢出用戶來并且發(fā)送郵件
List<Task> tasks = new ArrayList<>();
for (int i=0;i<100;i++){
//傳id進(jìn)去構(gòu)造不同的任務(wù)桶错,業(yè)務(wù)中有可能是給你個(gè)list列表
Task task = new Task(i);
tasks.add(task);
}
//返回任務(wù)執(zhí)行結(jié)果
List<Future<String>> futures = null;
//用線程池查詢用戶發(fā)送郵件
ExecutorService executorService = Executors.newFixedThreadPool(100);
try {
//是線程池執(zhí)行提交的批量任務(wù)
futures = executorService.invokeAll(tasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
//關(guān)閉線程池
executorService.shutdown();
//存放任務(wù)結(jié)果的集合
List<String> results = new ArrayList<>();
//遍歷這個(gè)任務(wù)執(zhí)行結(jié)果
for (Future<String> result:futures) {
//如果這個(gè)任務(wù)結(jié)束了
if (result.isDone()){
String s = null;
try {
//得到這個(gè)任務(wù)的處理結(jié)果,得不到會(huì)一直阻塞
s = result.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//將任務(wù)結(jié)果放進(jìn)任務(wù)結(jié)果集合里面
results.add(s);
}
}
//遍歷任務(wù)結(jié)果的集合
for (String s:results) {
System.out.println(s);
}
long concurrentEnd = System.currentTimeMillis();
System.out.println("多線程共用了"+(concurrentEnd-concurrentStart)+"ms");
}
}
我們最終會(huì)看到輸出結(jié)果為
發(fā)送成功0xhJaver.com
發(fā)送成功1xhJaver.com
發(fā)送成功2xhJaver.com
發(fā)送成功3xhJaver.com
發(fā)送成功4xhJaver.com
發(fā)送成功5xhJaver.com
發(fā)送成功6xhJaver.com
發(fā)送成功7xhJaver.com
發(fā)送成功8xhJaver.com
發(fā)送成功9xhJaver.com
發(fā)送成功10xhJaver.com
發(fā)送成功11xhJaver.com
發(fā)送成功12xhJaver.com
發(fā)送成功13xhJaver.com
發(fā)送成功14xhJaver.com
發(fā)送成功15xhJaver.com
發(fā)送成功16xhJaver.com
發(fā)送成功17xhJaver.com
發(fā)送成功18xhJaver.com
發(fā)送成功19xhJaver.com
發(fā)送成功20xhJaver.com
發(fā)送成功21xhJaver.com
發(fā)送成功22xhJaver.com
發(fā)送成功23xhJaver.com
發(fā)送成功24xhJaver.com
發(fā)送成功25xhJaver.com
發(fā)送成功26xhJaver.com
發(fā)送成功27xhJaver.com
發(fā)送成功28xhJaver.com
發(fā)送成功29xhJaver.com
發(fā)送成功30xhJaver.com
發(fā)送成功31xhJaver.com
發(fā)送成功32xhJaver.com
發(fā)送成功33xhJaver.com
發(fā)送成功34xhJaver.com
發(fā)送成功35xhJaver.com
發(fā)送成功36xhJaver.com
發(fā)送成功37xhJaver.com
發(fā)送成功38xhJaver.com
發(fā)送成功39xhJaver.com
發(fā)送成功40xhJaver.com
發(fā)送成功41xhJaver.com
發(fā)送成功42xhJaver.com
發(fā)送成功43xhJaver.com
發(fā)送成功44xhJaver.com
發(fā)送成功45xhJaver.com
發(fā)送成功46xhJaver.com
發(fā)送成功47xhJaver.com
發(fā)送成功48xhJaver.com
發(fā)送成功49xhJaver.com
發(fā)送成功50xhJaver.com
發(fā)送成功51xhJaver.com
發(fā)送成功52xhJaver.com
發(fā)送成功53xhJaver.com
發(fā)送成功54xhJaver.com
發(fā)送成功55xhJaver.com
發(fā)送成功56xhJaver.com
發(fā)送成功57xhJaver.com
發(fā)送成功58xhJaver.com
發(fā)送成功59xhJaver.com
發(fā)送成功60xhJaver.com
發(fā)送成功61xhJaver.com
發(fā)送成功62xhJaver.com
發(fā)送成功63xhJaver.com
發(fā)送成功64xhJaver.com
發(fā)送成功65xhJaver.com
發(fā)送成功66xhJaver.com
發(fā)送成功67xhJaver.com
發(fā)送成功68xhJaver.com
發(fā)送成功69xhJaver.com
發(fā)送成功70xhJaver.com
發(fā)送成功71xhJaver.com
發(fā)送成功72xhJaver.com
發(fā)送成功73xhJaver.com
發(fā)送成功74xhJaver.com
發(fā)送成功75xhJaver.com
發(fā)送成功76xhJaver.com
發(fā)送成功77xhJaver.com
發(fā)送成功78xhJaver.com
發(fā)送成功79xhJaver.com
發(fā)送成功80xhJaver.com
發(fā)送成功81xhJaver.com
發(fā)送成功82xhJaver.com
發(fā)送成功83xhJaver.com
發(fā)送成功84xhJaver.com
發(fā)送成功85xhJaver.com
發(fā)送成功86xhJaver.com
發(fā)送成功87xhJaver.com
發(fā)送成功88xhJaver.com
發(fā)送成功89xhJaver.com
發(fā)送成功90xhJaver.com
發(fā)送成功91xhJaver.com
發(fā)送成功92xhJaver.com
發(fā)送成功93xhJaver.com
發(fā)送成功94xhJaver.com
發(fā)送成功95xhJaver.com
發(fā)送成功96xhJaver.com
發(fā)送成功97xhJaver.com
發(fā)送成功98xhJaver.com
發(fā)送成功99xhJaver.com
單線程共用了18404ms
-------分割線-----------------分割線-----------------分割線-----------------分割線-----------------分割線----------
發(fā)送成功0xhJaver.com
發(fā)送成功1xhJaver.com
發(fā)送成功2xhJaver.com
發(fā)送成功3xhJaver.com
發(fā)送成功4xhJaver.com
發(fā)送成功5xhJaver.com
發(fā)送成功6xhJaver.com
發(fā)送成功7xhJaver.com
發(fā)送成功8xhJaver.com
發(fā)送成功9xhJaver.com
發(fā)送成功10xhJaver.com
發(fā)送成功11xhJaver.com
發(fā)送成功12xhJaver.com
發(fā)送成功13xhJaver.com
發(fā)送成功14xhJaver.com
發(fā)送成功15xhJaver.com
發(fā)送成功16xhJaver.com
發(fā)送成功17xhJaver.com
發(fā)送成功18xhJaver.com
發(fā)送成功19xhJaver.com
發(fā)送成功20xhJaver.com
發(fā)送成功21xhJaver.com
發(fā)送成功22xhJaver.com
發(fā)送成功23xhJaver.com
發(fā)送成功24xhJaver.com
發(fā)送成功25xhJaver.com
發(fā)送成功26xhJaver.com
發(fā)送成功27xhJaver.com
發(fā)送成功28xhJaver.com
發(fā)送成功29xhJaver.com
發(fā)送成功30xhJaver.com
發(fā)送成功31xhJaver.com
發(fā)送成功32xhJaver.com
發(fā)送成功33xhJaver.com
發(fā)送成功34xhJaver.com
發(fā)送成功35xhJaver.com
發(fā)送成功36xhJaver.com
發(fā)送成功37xhJaver.com
發(fā)送成功38xhJaver.com
發(fā)送成功39xhJaver.com
發(fā)送成功40xhJaver.com
發(fā)送成功41xhJaver.com
發(fā)送成功42xhJaver.com
發(fā)送成功43xhJaver.com
發(fā)送成功44xhJaver.com
發(fā)送成功45xhJaver.com
發(fā)送成功46xhJaver.com
發(fā)送成功47xhJaver.com
發(fā)送成功48xhJaver.com
發(fā)送成功49xhJaver.com
發(fā)送成功50xhJaver.com
發(fā)送成功51xhJaver.com
發(fā)送成功52xhJaver.com
發(fā)送成功53xhJaver.com
發(fā)送成功54xhJaver.com
發(fā)送成功55xhJaver.com
發(fā)送成功56xhJaver.com
發(fā)送成功57xhJaver.com
發(fā)送成功58xhJaver.com
發(fā)送成功59xhJaver.com
發(fā)送成功60xhJaver.com
發(fā)送成功61xhJaver.com
發(fā)送成功62xhJaver.com
發(fā)送成功63xhJaver.com
發(fā)送成功64xhJaver.com
發(fā)送成功65xhJaver.com
發(fā)送成功66xhJaver.com
發(fā)送成功67xhJaver.com
發(fā)送成功68xhJaver.com
發(fā)送成功69xhJaver.com
發(fā)送成功70xhJaver.com
發(fā)送成功71xhJaver.com
發(fā)送成功72xhJaver.com
發(fā)送成功73xhJaver.com
發(fā)送成功74xhJaver.com
發(fā)送成功75xhJaver.com
發(fā)送成功76xhJaver.com
發(fā)送成功77xhJaver.com
發(fā)送成功78xhJaver.com
發(fā)送成功79xhJaver.com
發(fā)送成功80xhJaver.com
發(fā)送成功81xhJaver.com
發(fā)送成功82xhJaver.com
發(fā)送成功83xhJaver.com
發(fā)送成功84xhJaver.com
發(fā)送成功85xhJaver.com
發(fā)送成功86xhJaver.com
發(fā)送成功87xhJaver.com
發(fā)送成功88xhJaver.com
發(fā)送成功89xhJaver.com
發(fā)送成功90xhJaver.com
發(fā)送成功91xhJaver.com
發(fā)送成功92xhJaver.com
發(fā)送成功93xhJaver.com
發(fā)送成功94xhJaver.com
發(fā)送成功95xhJaver.com
發(fā)送成功96xhJaver.com
發(fā)送成功97xhJaver.com
發(fā)送成功98xhJaver.com
發(fā)送成功99xhJaver.com
多線程共用了233ms
從輸出結(jié)果可以知道 單線程共用18404ms / 150 約等于122 多線程共用233ms /150 約等于1
就相當(dāng)于發(fā)用查詢發(fā)送一個(gè)人的時(shí)間解決了這100個(gè)人的問題胀蛮,具體的線程池核心大小數(shù)量要根據(jù)業(yè)務(wù)方面自己配置設(shè)計(jì)
這里面出現(xiàn)了很多和上一篇不會(huì)吧院刁,就是你把線程池講的這么清楚的?沒有講到的知識(shí)點(diǎn)绰上,感興趣的可以去看一看
二吏砂、例子重點(diǎn)講解
文中注釋也都清晰明了的,我在解釋下幾個(gè)重要的
2.1.創(chuàng)建任務(wù)
自定義任務(wù)類實(shí)現(xiàn)這個(gè)接口并且實(shí)現(xiàn)call方法苗胀,返回值為傳入Callable的類型即可
public class Task implements Callable<String> {
private Integer id;
public Task(Integer id) {
this.id = id;
}
@Override
public String call() throws Exception {
//調(diào)用業(yè)務(wù)方提供的查user的服務(wù)狡刘,id不同享潜,創(chuàng)建任務(wù)的時(shí)候就傳過來id
User user = DoSomethingService.queryUser(this.id);
//調(diào)用業(yè)務(wù)方提供發(fā)送郵件的服務(wù),email不同
String result = DoSomethingService.sendUserEmail(user.getEmail());
return result;
}
}
2.2.構(gòu)造的任務(wù)列表
//構(gòu)建要做的任務(wù)列表嗅蔬,查詢出用戶來并且發(fā)送郵件
List<Task> tasks = new ArrayList<>();
for (int i=0;i<100;i++){
//傳id進(jìn)去構(gòu)造不同的任務(wù)剑按,業(yè)務(wù)中有可能是給你個(gè)list列表
Task task = new Task(i);
tasks.add(task);
}
2.3.創(chuàng)建線程池提交任務(wù)列表并且關(guān)閉線程池
//用線程池查詢用戶發(fā)送郵件
ExecutorService executorService = Executors.newFixedThreadPool(100);
//返回任務(wù)執(zhí)行結(jié)果
List<Future<String>> futures = null;
try {
//是線程池執(zhí)行提交的批量任務(wù)
futures = executorService.invokeAll(tasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
//關(guān)閉線程池
executorService.shutdown();
注:提交任務(wù)的時(shí)候也可以是submit不過在這樣一次提交一個(gè)任務(wù),要是有任務(wù)列表可以用invokeAll shoutdown和shoutdownNow都可以關(guān)閉線程池澜术。但是又很大的區(qū)別 shoutdown :不會(huì)立刻停止線程池艺蝴,但是會(huì)拒絕處理新來的任務(wù),阻塞隊(duì)列中的任務(wù)等他執(zhí)行完 shoutdownNow:會(huì)立刻停止線程池鸟废,拒絕處理新來的任務(wù)并且打斷正在執(zhí)行的線程猜敢,將阻塞隊(duì)列中的任務(wù)全部清空 總的來說就是shoutdown溫柔一點(diǎn),shoutdownNow粗暴一點(diǎn)
2.3.1線程池提交callable任務(wù)四種方法講解
關(guān)于提交線程池提交callable任務(wù)有以下四種方法
- invokeAll無時(shí)間參數(shù)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
如果調(diào)沒有異常發(fā)生的話都會(huì)調(diào)用成功 如果有一個(gè)有異常則有異常的調(diào)用失敗盒延,其余成功
- invokeAll有時(shí)間參數(shù)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
當(dāng)全部任務(wù)執(zhí)行完后超過指定時(shí)限后缩擂,直接拋出異常
- invokeAny無時(shí)間參數(shù)
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
invokeAny將第一個(gè)完成的作為結(jié)果,或者調(diào)用失敗則也立即終止其他所有線程添寺。
- invokeAny有時(shí)間參數(shù)
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
若設(shè)置了超時(shí)時(shí)間胯盯,未超時(shí)完成則返回正常結(jié)果,否則報(bào)錯(cuò)
2.4.解析任務(wù)結(jié)果
//存放任務(wù)結(jié)果的集合
List<String> results = new ArrayList<>();
//遍歷這個(gè)任務(wù)執(zhí)行結(jié)果
for (Future<String> result:futures) {
//如果這個(gè)任務(wù)結(jié)束了
if (result.isDone()){
String s = null;
try {
//得到這個(gè)任務(wù)的處理結(jié)果畦贸,得不到會(huì)一直阻塞
s = result.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//將任務(wù)結(jié)果放進(jìn)任務(wù)結(jié)果集合里面
results.add(s);
}
}
//遍歷任務(wù)結(jié)果的集合
for (String s:results) {
System.out.println(s);
}
三陨闹、future接口方法講解
注:future是個(gè)jdk1.5以后出現(xiàn)的異步任務(wù)接口
public interface Future<V> {
//若此任務(wù)已經(jīng)完成或者被取消或者因?yàn)槠渌虿荒鼙蝗∠麆t取消失敗,
//如果此任務(wù)還沒有開始或者已經(jīng)開始這個(gè)時(shí)候就由這個(gè)參數(shù)來覺得取消與否
// 在這個(gè)方法執(zhí)行完后薄坏,isDone,iscancel總是返回true,
//如果任務(wù)無法取消經(jīng)常是因?yàn)槿蝿?wù)被執(zhí)行完了
boolean cancel(boolean mayInterruptIfRunning);
//是否取消成功
boolean isCancelled();
//任務(wù)是否完成趋厉,正常結(jié)束取消或者異常,都返回true
boolean isDone();
//得到這個(gè)任務(wù)的結(jié)果胶坠,會(huì)一直阻塞到得到結(jié)果
//如果任務(wù)被取消君账,則拋出CancellationException 異常
//若任務(wù)有異常,則拋出ExecutionException 異常
//若任務(wù)被中斷則拋出InterruptedException 異常
V get() throws InterruptedException, ExecutionException;
//得到這個(gè)任務(wù)的結(jié)果沈善,若超過時(shí)間會(huì)拋異常乡数,除此之外和上面那個(gè)方法一樣
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
我們文中的例子用到了 V get()這個(gè),下一篇我打算寫一寫線程池為什么可以做到線程復(fù)用闻牡? 源碼面前净赴,沒有秘密
更多精彩請(qǐng)關(guān)注公眾號(hào)xhJaver,京東java工程師和你一起成長(zhǎng)