Java多線程--讓主線程等待所有子線程執(zhí)行完畢在執(zhí)行
朋友讓我?guī)兔憘€(gè)程序從文本文檔中導(dǎo)入數(shù)據(jù)到oracle數(shù)據(jù)庫(kù)中,技術(shù)上沒(méi)有什么難度,文檔的格式都是固定的只要對(duì)應(yīng)數(shù)據(jù)庫(kù)中的字段解析就行了,關(guān)鍵在于性能。
數(shù)據(jù)量很大百萬(wàn)條記錄,因此考慮到要用多線程并發(fā)執(zhí)行,在寫的過(guò)程中又遇到問(wèn)題,我想統(tǒng)計(jì)所有子進(jìn)程執(zhí)行完畢總共的耗時(shí),在第一個(gè)子進(jìn)程創(chuàng)建前記錄當(dāng)前時(shí)間用System.currentTimeMillis()在最后一個(gè)子進(jìn)程結(jié)束后記錄當(dāng)前時(shí)間,兩次一減得到的時(shí)間差即為總共的用時(shí),代碼如下
long tStart = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + "開(kāi)始");//打印開(kāi)始標(biāo)記 for (int ii = 0; ii < threadNum; ii++) {//開(kāi)threadNum個(gè)線程 Runnable r = new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName() + "開(kāi)始"); //做一些事情... ... System.out.println(Thread.currentThread().getName() + "結(jié)束."); } } Thread t = new Thread(r); t.start(); } System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標(biāo)記 long tEnd = System.currentTimeMillis(); System.out.println("總共用時(shí):"+ (tEnd - tStart) + "millions");
結(jié)果是幾乎在for循環(huán)結(jié)束的瞬間就執(zhí)行了主線程打印總共用時(shí)的語(yǔ)句,原因是所有的子線程是并發(fā)執(zhí)行的,它們運(yùn)行時(shí)主線程也在運(yùn)行,這就引出了一個(gè)問(wèn)題即本文標(biāo)題如何"讓主線程等待所有子線程執(zhí)行完畢"。試過(guò)在每個(gè)子線程開(kāi)始后加上t.join(),結(jié)果是所有線程都順序執(zhí)行,這就失去了并發(fā)的意義了,顯然不是我想要的。
網(wǎng)上Google了很久也沒(méi)有找到解決方案,難道就沒(méi)有人遇到過(guò)這種需求嗎?還是這個(gè)問(wèn)題太簡(jiǎn)單了?無(wú)耐只得自己想辦法了...
最后我的解決辦法是,自定義一個(gè)ImportThread類繼承自java.lang.Thread,重載run()方法,用一個(gè)List屬性保存所有產(chǎn)生的線程,這樣只要判斷這個(gè)List是否為空就知道還有沒(méi)有子線程沒(méi)有執(zhí)行完了,類代碼如下:
public class ImportThread extends Thread { private static List<Thread> runningThreads = new ArrayList<Thread>(); public ImportThread() { } @Override public void run() { regist(this);//線程開(kāi)始時(shí)注冊(cè) System.out.println(Thread.currentThread().getName() + "開(kāi)始...");//打印開(kāi)始標(biāo)記 //做一些事情... ... unRegist(this);//線程結(jié)束時(shí)取消注冊(cè) System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標(biāo)記 } public void regist(Thread t){ synchronized(runningThreads){ runningThreads.add(t); } } public void unRegist(Thread t){ synchronized(runningThreads){ runningThreads.remove(t); } } public static boolean hasThreadRunning() { return (runningThreads.size() > 0);//通過(guò)判斷runningThreads是否為空就能知道是否還有線程未執(zhí)行完 } }
主線程中代碼:
long tStart = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + "開(kāi)始");//打印開(kāi)始標(biāo)記 for (int ii = 0; ii < threadNum; ii++) {//開(kāi)threadNum個(gè)線程 Thread t = new ImportThread(); t.start(); } while(true){//等待所有子線程執(zhí)行完 if(!ImportThread.hasThreadRunning()){ break; } Thread.sleep(500); } System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標(biāo)記 long tEnd = System.currentTimeMillis(); System.out.println("總共用時(shí):"+ (tEnd - tStart) + "millions");
打印的結(jié)果是:
main開(kāi)始
Thread-1開(kāi)始...
Thread-5開(kāi)始...
Thread-0開(kāi)始...
Thread-2開(kāi)始...
Thread-3開(kāi)始...
Thread-4開(kāi)始...
Thread-5結(jié)束.
Thread-4結(jié)束.
Thread-2結(jié)束.
Thread-0結(jié)束.
Thread-3結(jié)束.
Thread-1結(jié)束.
main結(jié)束.
總共用時(shí):20860millions
可以看到main線程是等所有子線程全部執(zhí)行完后才開(kāi)始執(zhí)行的。
=================================================================================================
上面的方法有一個(gè)隱患:如果線程1開(kāi)始并且結(jié)束了,而其他線程還沒(méi)有開(kāi)始此時(shí)runningThreads的size也為0,主線程會(huì)以為所有線程都執(zhí)行完了。解決辦法是用一個(gè)非簡(jiǎn)單類型的計(jì)數(shù)器來(lái)取代List型的runningThreads,并且在線程創(chuàng)建之前就應(yīng)該設(shè)定好計(jì)數(shù)器的值。
MyCountDown類
public class MyCountDown { private int count; public MyCountDown(int count){ this.count = count; } public synchronized void countDown(){ count--; } public synchronized boolean hasNext(){ return (count > 0); } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
ImportThread類
public class ImportThread extends Thread { private MyCountDown c; public ImportThread(MyCountDown c) { this.c = c; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "開(kāi)始...");//打印開(kāi)始標(biāo)記 //Do something c.countDown();//計(jì)時(shí)器減1 System.out.println(Thread.currentThread().getName() + "結(jié)束. 還有" + c.getCount() + " 個(gè)線程");//打印結(jié)束標(biāo)記 } }
主線程中
System.out.println(Thread.currentThread().getName() + "開(kāi)始");//打印開(kāi)始標(biāo)記 MyCountDown c = new MyCountDown(threadNum);//初始化countDown for (int ii = 0; ii < threadNum; ii++) {//開(kāi)threadNum個(gè)線程 Thread t = new ImportThread(c); t.start(); } while(true){//等待所有子線程執(zhí)行完 if(!c.hasNext()) break; } System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標(biāo)記
打印結(jié)果:
main開(kāi)始
Thread-2開(kāi)始...
Thread-1開(kāi)始...
Thread-0開(kāi)始...
Thread-3開(kāi)始...
Thread-5開(kāi)始...
Thread-4開(kāi)始...
Thread-5結(jié)束. 還有5 個(gè)線程
Thread-1結(jié)束. 還有4 個(gè)線程
Thread-4結(jié)束. 還有3 個(gè)線程
Thread-2結(jié)束. 還有2 個(gè)線程
Thread-3結(jié)束. 還有1 個(gè)線程
Thread-0結(jié)束. 還有0 個(gè)線程
main結(jié)束.
更簡(jiǎn)單的方法:使用java.util.concurrent.CountDownLatch代替MyCountDown,用await()方法代替while(true){...}
ImportThread類
public class ImportThread extends Thread { private CountDownLatch threadsSignal; public ImportThread(CountDownLatch threadsSignal) { this.threadsSignal = threadsSignal; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "開(kāi)始..."); //Do somethings threadsSignal.countDown();//線程結(jié)束時(shí)計(jì)數(shù)器減1 System.out.println(Thread.currentThread().getName() + "結(jié)束. 還有" + threadsSignal.getCount() + " 個(gè)線程"); } }
主線程中
CountDownLatch threadSignal = new CountDownLatch(threadNum);//初始化countDown for (int ii = 0; ii < threadNum; ii++) {//開(kāi)threadNum個(gè)線程 final Iterator<String> itt = it.get(ii); Thread t = new ImportThread(itt,sql,threadSignal); t.start(); } threadSignal.await();//等待所有子線程執(zhí)行完 System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標(biāo)記
打印結(jié)果:
main開(kāi)始
Thread-1開(kāi)始...
Thread-0開(kāi)始...
Thread-2開(kāi)始...
Thread-3開(kāi)始...
Thread-4開(kāi)始...
Thread-5開(kāi)始...
Thread-0結(jié)束. 還有5 個(gè)線程
Thread-1結(jié)束. 還有4 個(gè)線程
Thread-4結(jié)束. 還有3 個(gè)線程
Thread-2結(jié)束. 還有2 個(gè)線程
Thread-5結(jié)束. 還有1 個(gè)線程
Thread-3結(jié)束. 還有0 個(gè)線程
main結(jié)束.
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Spring Boot 中實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式
這篇文章主要介紹了Spring Boot 中實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04java開(kāi)源調(diào)度如何給xxljob加k8s執(zhí)行器
這篇文章主要介紹了java開(kāi)源調(diào)度如何給xxljob加一個(gè)k8s執(zhí)行器,?xxljob?在設(shè)計(jì)上,抽象出了執(zhí)行器的接口,所以實(shí)現(xiàn)一個(gè)語(yǔ)言的執(zhí)行器并不復(fù)雜,這里主要探索下,如何利用k8s的pod?的能力,使用?xxljob?調(diào)度?pod?運(yùn)行,實(shí)現(xiàn)一個(gè)通用的和語(yǔ)言無(wú)關(guān)的執(zhí)行器2022-02-02java實(shí)現(xiàn)HttpClient異步請(qǐng)求資源的方法
這篇文章主要介紹了java實(shí)現(xiàn)HttpClient異步請(qǐng)求資源的方法,實(shí)例分析了java基于http協(xié)議實(shí)現(xiàn)異步請(qǐng)求的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07Java內(nèi)存模型相關(guān)知識(shí)總結(jié)
這篇文章主要介紹了Java內(nèi)存模型相關(guān)知識(shí)總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10springboot整合log4j的踩坑實(shí)戰(zhàn)記錄
log日志的重要性不言而喻,所以我們需要在系統(tǒng)內(nèi)根據(jù)實(shí)際的業(yè)務(wù)進(jìn)行日志的整合,下面這篇文章主要給大家介紹了關(guān)于springboot整合log4j的踩坑實(shí)戰(zhàn)記錄,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04SpringAOP 設(shè)置注入的實(shí)現(xiàn)步驟
這篇文章主要介紹了SpringAOP 設(shè)置注入的實(shí)現(xiàn)步驟,幫助大家更好的理解和學(xué)習(xí)使用Spring框架,感興趣的朋友可以了解下2021-05-05SpringBoot統(tǒng)一接口返回及全局異常處理高級(jí)用法
這篇文章主要為大家介紹了SpringBoot統(tǒng)一接口返回及全局異常處理高級(jí)用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06