Java多線程--讓主線程等待所有子線程執(zhí)行完畢在執(zhí)行
朋友讓我?guī)兔憘€程序從文本文檔中導入數(shù)據(jù)到oracle數(shù)據(jù)庫中,技術(shù)上沒有什么難度,文檔的格式都是固定的只要對應(yīng)數(shù)據(jù)庫中的字段解析就行了,關(guān)鍵在于性能。
數(shù)據(jù)量很大百萬條記錄,因此考慮到要用多線程并發(fā)執(zhí)行,在寫的過程中又遇到問題,我想統(tǒng)計所有子進程執(zhí)行完畢總共的耗時,在第一個子進程創(chuàng)建前記錄當前時間用System.currentTimeMillis()在最后一個子進程結(jié)束后記錄當前時間,兩次一減得到的時間差即為總共的用時,代碼如下
long tStart = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "開始");//打印開始標記
for (int ii = 0; ii < threadNum; ii++) {//開threadNum個線程
Runnable r = new Runnable(){
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "開始");
//做一些事情... ...
System.out.println(Thread.currentThread().getName() + "結(jié)束.");
}
}
Thread t = new Thread(r);
t.start();
}
System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標記
long tEnd = System.currentTimeMillis();
System.out.println("總共用時:"+ (tEnd - tStart) + "millions");
結(jié)果是幾乎在for循環(huán)結(jié)束的瞬間就執(zhí)行了主線程打印總共用時的語句,原因是所有的子線程是并發(fā)執(zhí)行的,它們運行時主線程也在運行,這就引出了一個問題即本文標題如何"讓主線程等待所有子線程執(zhí)行完畢"。試過在每個子線程開始后加上t.join(),結(jié)果是所有線程都順序執(zhí)行,這就失去了并發(fā)的意義了,顯然不是我想要的。
網(wǎng)上Google了很久也沒有找到解決方案,難道就沒有人遇到過這種需求嗎?還是這個問題太簡單了?無耐只得自己想辦法了...
最后我的解決辦法是,自定義一個ImportThread類繼承自java.lang.Thread,重載run()方法,用一個List屬性保存所有產(chǎn)生的線程,這樣只要判斷這個List是否為空就知道還有沒有子線程沒有執(zhí)行完了,類代碼如下:
public class ImportThread extends Thread {
private static List<Thread> runningThreads = new ArrayList<Thread>();
public ImportThread() {
}
@Override
public void run() {
regist(this);//線程開始時注冊
System.out.println(Thread.currentThread().getName() + "開始...");//打印開始標記
//做一些事情... ...
unRegist(this);//線程結(jié)束時取消注冊
System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標記
}
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);//通過判斷runningThreads是否為空就能知道是否還有線程未執(zhí)行完
}
}
主線程中代碼:
long tStart = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "開始");//打印開始標記
for (int ii = 0; ii < threadNum; ii++) {//開threadNum個線程
Thread t = new ImportThread();
t.start();
}
while(true){//等待所有子線程執(zhí)行完
if(!ImportThread.hasThreadRunning()){
break;
}
Thread.sleep(500);
}
System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標記
long tEnd = System.currentTimeMillis();
System.out.println("總共用時:"+ (tEnd - tStart) + "millions");
打印的結(jié)果是:
main開始
Thread-1開始...
Thread-5開始...
Thread-0開始...
Thread-2開始...
Thread-3開始...
Thread-4開始...
Thread-5結(jié)束.
Thread-4結(jié)束.
Thread-2結(jié)束.
Thread-0結(jié)束.
Thread-3結(jié)束.
Thread-1結(jié)束.
main結(jié)束.
總共用時:20860millions
可以看到main線程是等所有子線程全部執(zhí)行完后才開始執(zhí)行的。
=================================================================================================
上面的方法有一個隱患:如果線程1開始并且結(jié)束了,而其他線程還沒有開始此時runningThreads的size也為0,主線程會以為所有線程都執(zhí)行完了。解決辦法是用一個非簡單類型的計數(shù)器來取代List型的runningThreads,并且在線程創(chuàng)建之前就應(yīng)該設(shè)定好計數(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() + "開始...");//打印開始標記
//Do something
c.countDown();//計時器減1
System.out.println(Thread.currentThread().getName() + "結(jié)束. 還有" + c.getCount() + " 個線程");//打印結(jié)束標記
}
}
主線程中
System.out.println(Thread.currentThread().getName() + "開始");//打印開始標記
MyCountDown c = new MyCountDown(threadNum);//初始化countDown
for (int ii = 0; ii < threadNum; ii++) {//開threadNum個線程
Thread t = new ImportThread(c);
t.start();
}
while(true){//等待所有子線程執(zhí)行完
if(!c.hasNext()) break;
}
System.out.println(Thread.currentThread().getName() + "結(jié)束.");//打印結(jié)束標記
打印結(jié)果:
main開始
Thread-2開始...
Thread-1開始...
Thread-0開始...
Thread-3開始...
Thread-5開始...
Thread-4開始...
Thread-5結(jié)束. 還有5 個線程
Thread-1結(jié)束. 還有4 個線程
Thread-4結(jié)束. 還有3 個線程
Thread-2結(jié)束. 還有2 個線程
Thread-3結(jié)束. 還有1 個線程
Thread-0結(jié)束. 還有0 個線程
main結(jié)束.
更簡單的方法:使用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() + "開始...");
//Do somethings
threadsSignal.countDown();//線程結(jié)束時計數(shù)器減1
System.out.println(Thread.currentThread().getName() + "結(jié)束. 還有" + threadsSignal.getCount() + " 個線程");
}
}
主線程中
CountDownLatch threadSignal = new CountDownLatch(threadNum);//初始化countDown
for (int ii = 0; ii < threadNum; ii++) {//開threadNum個線程
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é)束標記
打印結(jié)果:
main開始
Thread-1開始...
Thread-0開始...
Thread-2開始...
Thread-3開始...
Thread-4開始...
Thread-5開始...
Thread-0結(jié)束. 還有5 個線程
Thread-1結(jié)束. 還有4 個線程
Thread-4結(jié)束. 還有3 個線程
Thread-2結(jié)束. 還有2 個線程
Thread-5結(jié)束. 還有1 個線程
Thread-3結(jié)束. 還有0 個線程
main結(jié)束.
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java后端接口中提取請求頭中的Cookie和Token的方法
在現(xiàn)代 Web 開發(fā)中,HTTP 請求頭(Header)是客戶端與服務(wù)器之間傳遞信息的重要方式之一,本文將詳細介紹如何在 Java 后端(以 Spring Boot 為例)中提取請求頭中的 Cookie 和 Token,并提供完整的代碼示例和優(yōu)化建議,需要的朋友可以參考下2025-01-01
PowerJob?AbstractSqlProcessor方法工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob?AbstractSqlProcessor方法工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
spring boot+mybatis 多數(shù)據(jù)源切換(實例講解)
下面小編就為大家?guī)硪黄猻pring boot+mybatis 多數(shù)據(jù)源切換(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
詳解在spring boot中配置多個DispatcherServlet
本篇文章主要介紹了詳解在spring boot中配置多個DispatcherServlet,具有一定的參考價值,有興趣的可以了解一下。2017-03-03
Java springboot 配置文件與多環(huán)境配置與運行優(yōu)先級
這篇文章主要介紹了Java springboot如何配置文件,進行多環(huán)境配置,以及運行優(yōu)先級,感興趣的小伙伴可以借鑒一下2023-04-04
解讀springboot配置mybatis的sql執(zhí)行超時時間(mysql)
這篇文章主要介紹了解讀springboot配置mybatis的sql執(zhí)行超時時間(mysql),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01

