詳解一道京東面試題
多線程并發(fā)執(zhí)行?線程之間通信?這是京東我偶爾聽到我同事做面試官時(shí)問的一道題,感覺很有意思,道面發(fā)出來大家和大家討論下
面試題目描述
現(xiàn)在呢,我們有三個(gè)接口,試題就叫他A,B,C吧,京東這三個(gè)接口都是道面查詢某個(gè)人征信信息的,必須同時(shí)返回true,我們才認(rèn)為這個(gè)人的試題征信合格,如果其中某一個(gè)返回false的京東話,就表明這個(gè)人的道面征信不合格,如果是試題你,你會怎么設(shè)計(jì)怎么寫這個(gè)代碼呢?
第一次思考
首先,京東一定是道面并發(fā)執(zhí)行,假如說A接口執(zhí)行3秒,試題B接口執(zhí)行5秒,C接口執(zhí)行8秒的話
- 串行執(zhí)行:3+5+8 = 16秒
- 并發(fā)執(zhí)行:8=8秒 (時(shí)間最久的那個(gè)接口執(zhí)行的時(shí)間就是這三個(gè)接口的執(zhí)行總時(shí)間)
熟悉的感覺,多線程執(zhí)行任務(wù),我在第二章文章實(shí)戰(zhàn)!xhJaver竟然用線程池優(yōu)化了。。。有提過怎么寫,感興趣的讀者可以回去看一下,不過我在這里再寫一下,話不多說來看下代碼
并發(fā)代碼
建議用PC端查看,所有代碼都可直接復(fù)制運(yùn)行,代碼中重要的點(diǎn)都有詳細(xì)注釋
- 首先,我們先定義這三個(gè)接口
public class DoService { //設(shè)置A?B?C接口的返回值,b接口設(shè)置的是false private static Boolean?flagA?= true; private static Boolean?flagB?= false; private static Boolean?flagC?= true; public static boolean A(){ long start?=?System.currentTimeMillis(); try { ????????????Thread.sleep(3000L);????????} catch (InterruptedException?e)?{ ????????????System.out.println("a被打斷??耗時(shí)" +?(System.currentTimeMillis()?-?start));???????????????e.printStackTrace();????????}????????System.out.println("a耗時(shí)??"+(System.currentTimeMillis()?-?start)); return flagA;????} public static boolean B() { long start?=?System.currentTimeMillis(); try { ????????????Thread.sleep(5000L);????????} catch (InterruptedException?e)?{ ????????????System.out.println("b被打斷??耗時(shí)" +?(System.currentTimeMillis()?-?start));????????????e.printStackTrace();????????}????????System.out.println("b耗時(shí)??"+(System.currentTimeMillis()?-?start)); return flagB;????} public static boolean C() { long start?=?System.currentTimeMillis(); try { ????????????Thread.sleep(8000L);????????} catch (InterruptedException?e)?{ ????????????System.out.println("c被打斷??耗時(shí)" +?(System.currentTimeMillis()?-?start));????????????e.printStackTrace();????????}????????System.out.println("c耗時(shí)??"+(System.currentTimeMillis()?-?start)); return flagC;????}}
- 其次 我們先創(chuàng)造一個(gè)Task 任務(wù)類
public class Task implements Callable{ private String?taskName; private Integer?i; public Task(String?taskName,int i){ this.taskName?=taskName; this.i?=?i;????} @Override public Boolean call() throws Exception { //?標(biāo)記?返回值,代表這個(gè)接口是否執(zhí)行成功 Boolean?flag?= false; //記錄接口名字 String?serviceName?= null; //根據(jù)i的值來判斷調(diào)用哪個(gè)接口 if (i==1){ ????????????flag???=????DoService.A();????????????serviceName="A";????????} if (i==2){ ????????????flag???=????DoService.B();????????????serviceName="B";????????} if (i==3){ ????????????flag???=????DoService.C();????????????serviceName="C";????????}????????System.out.println("當(dāng)前線程是:?"+Thread.currentThread().getName()+"正在處理的任務(wù)是:?"+this.taskName+"調(diào)用的接口是:?"+serviceName); return flag;????}}
- 最后,我們定義一個(gè)測試類
class Test { public static void main(String[]?args) throws ExecutionException,?InterruptedException { //創(chuàng)建一個(gè)包含三個(gè)線程的線程池 ExecutorService?executorService?=?Executors.newFixedThreadPool(3); //事先準(zhǔn)備好儲存結(jié)果的list集合 List< Future>?list?= new ArrayList<>(); //開始計(jì)時(shí) long start?=?System.currentTimeMillis(); for (int i=1;i<4;i++){ ????????????Task?task?= new Task("任務(wù)"+i,i); //將每個(gè)任務(wù)提交到線程池中,并且得到這個(gè)線程的執(zhí)行結(jié)果 Futureresult?=?executorService.submit(task);????????????list.add(result);????????} //記得把線程池關(guān)閉 executorService.shutdown(); //定義一個(gè)變量?0 int count?= 0;????????System.out.println("等待處理結(jié)果。。。"); for (int i=0;i
- 我們看下輸出結(jié)果
等待處理結(jié)果。。。a耗時(shí) 3000 當(dāng)前線程是:?pool-1-thread-1正在處理的任務(wù)是:?任務(wù)1調(diào)用的接口是:?Ab耗時(shí) 5000 當(dāng)前線程是:?pool-1-thread-2正在處理的任務(wù)是:?任務(wù)2調(diào)用的接口是:?Bc耗時(shí) 8000 當(dāng)前線程是:?pool-1-thread-3正在處理的任務(wù)是:?任務(wù)3調(diào)用的接口是:?C線程池+結(jié)果處理時(shí)間:8008 不合格
- 我們運(yùn)行的時(shí)候會發(fā)現(xiàn),它的輸出結(jié)果的順序如下 1 2 3 4 5我們圖中的2,3,4是再線程池內(nèi)開了三個(gè)線程執(zhí)行的,他們之間相隔一段時(shí)間才出現(xiàn)的,因?yàn)槊總€(gè)接口都有執(zhí)行時(shí)間
程序運(yùn)行后,“標(biāo)記2”是3秒后出現(xiàn),“標(biāo)記三”是5秒后出現(xiàn),“標(biāo)記4”是8秒后出現(xiàn)
其實(shí)4和5相差時(shí)間很短,幾乎是同時(shí)出現(xiàn)的,因?yàn)?執(zhí)行完了就是主線程繼續(xù)執(zhí)行了
線程池+結(jié)果處理的時(shí)間一共是8秒,而每個(gè)接口分別執(zhí)行的時(shí)間是3秒,5秒,8秒,達(dá)到了我們所說的,多線程處理多個(gè)接口,總共耗時(shí)時(shí)間是耗時(shí)最長的接口的時(shí)間
和京東面試官探討
波哥說(我愛叫他波哥,東北人,說話則逗,幽默的人簡直就是人間瑰寶,其實(shí)我也蠻有趣的,就是沒人發(fā)現(xiàn)),你這程序不行啊,有個(gè)缺點(diǎn),假如說,你這個(gè)A接口,耗時(shí)三秒,他返回了false,那么你另外兩個(gè)線程也不用執(zhí)行了,這個(gè)人的征信已經(jīng)不合格了,你需要判斷下,如果某一個(gè)線程執(zhí)行的任務(wù)返回了false,那么就及時(shí)中斷其他兩個(gè)線程
靈光乍現(xiàn)
上一次的代碼已經(jīng)實(shí)現(xiàn)了多線程執(zhí)行任務(wù),可是這線程間通信怎么辦呢?怎么才能根據(jù)一個(gè)線程的執(zhí)行結(jié)果而打斷其他線程呢?我想到了以下幾點(diǎn)
共享變量
public static volatile boolean end = true;
這個(gè)共享變量就代表是否結(jié)束三個(gè)線程的執(zhí)行 如果為true的話,代表結(jié)束,false的話代表不結(jié)束線程執(zhí)行
計(jì)數(shù)器
public static AtomicInteger count =new AtomicInteger(0);
每當(dāng)每個(gè)線程執(zhí)行完的話,如果返回true,計(jì)數(shù)器就+1,當(dāng)計(jì)數(shù)器變?yōu)?的時(shí)候,就代表這個(gè)人征信沒問題
中斷方法
interrupt()
我們會單獨(dú)開個(gè)線程一直循環(huán)檢測這個(gè)變量,當(dāng)檢測到為true的時(shí)候,就會調(diào)用中斷方法中斷這三個(gè)線程
阻塞線程
countDownLatch
我們程序往下執(zhí)行需要獲取結(jié)果,獲取不到這個(gè)結(jié)果的話,就要一直等著。我們可以用這個(gè)線程阻塞的工具,一開始給他設(shè)置數(shù)量為1,當(dāng)滿足繼續(xù)向下執(zhí)行的條件時(shí),調(diào)用
countDownLatch.countDown();,在主線程那里
countDownLatch.await();一下這樣當(dāng)檢測到數(shù)量為0的時(shí)候,主線程那里就繼續(xù)往下執(zhí)行了,話不多說,來看代碼
代碼優(yōu)化
代碼優(yōu)化
建議用PC端查看,所有代碼都可直接復(fù)制運(yùn)行,代碼中重要的點(diǎn)都有詳細(xì)注釋
首先,還是創(chuàng)建接口
public class DoService { private static Boolean?flagA?= true; private static Boolean?flagB?= false; private static Boolean?flagC?= true; public static boolean A(){ long start?=?System.currentTimeMillis(); try { ????????????Thread.sleep(3000L);????????} catch (InterruptedException?e)?{ ????????????System.out.println("a被打斷??耗時(shí)" +?(System.currentTimeMillis()?-?start));???????????????e.printStackTrace();????????}????????System.out.println("a耗時(shí)??"+(System.currentTimeMillis()?-?start)); return flagA;????} public static boolean B() { long start?=?System.currentTimeMillis(); try { ????????????Thread.sleep(5000L);????????} catch (InterruptedException?e)?{ ????????????System.out.println("b被打斷??耗時(shí)" +?(System.currentTimeMillis()?-?start));????????????e.printStackTrace();????????}????????System.out.println("b耗時(shí)??"+(System.currentTimeMillis()?-?start)); return flagB;????} public static boolean C() { long start?=?System.currentTimeMillis(); try { ????????????Thread.sleep(8000L);????????} catch (InterruptedException?e)?{ ????????????System.out.println("c被打斷??耗時(shí)" +?(System.currentTimeMillis()?-?start));????????????e.printStackTrace();????????}????????System.out.println("c耗時(shí)??"+(System.currentTimeMillis()?-?start)); return flagC;????}}
- 創(chuàng)建任務(wù)
public class Task implements Runnable { private String?name?; public Task(?String?name){ this.name?=?name;????} @Override public void run() { boolean flag?= false;????????String?serviceName?= null; if(this.name.equals("A")){ ????????????serviceName?= "A";?????????????flag?=?DoService.A();????????} if(this.name.equals("B")){ ????????????serviceName?= "B";????????????flag?=?DoService.B();????????} if(this.name.equals("C")){ ???????????serviceName?= "C";???????????flag?=?DoService.C();???????} //如果有一個(gè)為false if (!flag){ //就把共享標(biāo)志位置為false Test.end?= false;????????}else { //計(jì)數(shù)器加一,到三的話就是三個(gè)都為true Test.count.incrementAndGet();???????}????????System.out.println("當(dāng)前線程是:?"+Thread.currentThread().getName()+"正在處理的任務(wù)是:?"+this.name+"調(diào)用的接口是:?"+serviceName);????}}
- 創(chuàng)建測試類
class Test { //設(shè)置countDownLatch?里面計(jì)數(shù)為1, //?只調(diào)用一次countDownLatch.countDown就可以繼續(xù)執(zhí)行?countDownLatch.await(); //后面的代碼了,接觸阻塞 public static CountDownLatch?countDownLatch?= new CountDownLatch(1); //默認(rèn)都為true,有一個(gè)線程為false了,那么就變?yōu)閒alse public static volatile boolean end?= true; //計(jì)數(shù)器,數(shù)字變?yōu)?的時(shí)候代表三個(gè)接口都返回true,線程安全的原子類 public static AtomicInteger?count?=new AtomicInteger(0); public static void main(String[]?args) throws InterruptedException { long start?=?System.currentTimeMillis(); //創(chuàng)建三個(gè)任務(wù),分被調(diào)用A?B?C?接口 Task?taskA?= new Task("A");????????Task?taskB?= new Task("B");????????Task?taskC?= new Task("C"); //創(chuàng)建三個(gè)線程 Thread?tA?= new Thread(taskA);????????Thread?tB?= new Thread(taskB);????????Thread?tC?= new Thread(taskC); //開啟三個(gè)線程 tA.start();????????tB.start();????????tC.start(); //在開啟一個(gè)線程,這個(gè)線程就是單獨(dú)循環(huán)掃描這個(gè)共享變量的 new Thread(new Runnable()?{ @Override public void run() { //此線程一直循環(huán)判斷這個(gè)結(jié)束變量,如果為false的話,就代表有一個(gè)接口返回false,跳出,重點(diǎn)其他線程 while (true){ if (!end?){ //當(dāng)這個(gè)共享變量為false時(shí)i表示,其他線程可以中斷了,所以就打斷他們執(zhí)行 tA.interrupt();????????????????????????tB.interrupt();????????????????????????tC.interrupt(); //如果某個(gè)線程被打斷的話,就表明不合格 System.out.println("不合格"); //countDownLatch?計(jì)數(shù)器減一 countDownLatch.countDown(); break;????????????????????} if (Test.count.get()==3){ ????????????????????????System.out.println("合格"); //countDownLatch?計(jì)數(shù)器減一 countDownLatch.countDown(); break;????????????????????}????????????????}????????????}????????}).start();????????System.out.println(Thread.currentThread().getName()+"主線程開始掛起"); //阻塞主線程繼續(xù)執(zhí)行,等待其他線程計(jì)算完結(jié)果在執(zhí)行下去,countDownLatch中的計(jì)數(shù)為0時(shí),就可以繼續(xù)執(zhí)行下去 countDownLatch.await();????????System.out.println(Thread.currentThread().getName()+"?主線獲得結(jié)果后繼續(xù)執(zhí)行"+(System.currentTimeMillis()?-?start));????}}
- 我們看下輸出結(jié)果
main主線程開始掛起a耗時(shí) 3024 當(dāng)前線程是:?Thread-0正在處理的任務(wù)是:?A調(diào)用的接口是:?Ab耗時(shí) 5000 當(dāng)前線程是:?Thread-1正在處理的任務(wù)是:?B調(diào)用的接口是:?Bc被打斷??耗時(shí)5001 不合格java.lang.InterruptedException:?sleep?interrupted?at?java.lang.Thread.sleep(Native?Method)?at?com.xhj.concurrent.executor_05._02.DoService.C(DoService.java:41)?at?com.xhj.concurrent.executor_05._02.Task.run(Task.java:30)?at?java.lang.Thread.run(Thread.java:748)c耗時(shí) 5003 當(dāng)前線程是:?Thread-2正在處理的任務(wù)是:?C調(diào)用的接口是:?Cmain?主線獲得結(jié)果后繼續(xù)執(zhí)行5014
- 我們運(yùn)行的時(shí)候會發(fā)現(xiàn)
由圖可見,我們首先就把主線程掛起,等待其他四個(gè)線程的處理結(jié)果,三個(gè)線程分別處理那三個(gè)接口,另外一個(gè)線程循環(huán)遍歷那個(gè)共享變量,當(dāng)檢測到為false時(shí),及時(shí)打斷其他線程,這樣的話,就解決了上面的那個(gè)問題。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!