標籤:
資料量很大百萬條記錄,因此考慮到要用多線程並發執行,在寫的過程中又遇到問題,我想統計所有子進程執行完畢總共的耗時,在第一個子進程建立前記錄目前時間用System.currentTimeMillis()在最後一個子進程結束後記錄目前時間,兩次一減得到的時間差即為總共的用時,代碼如下
Java代碼
- 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() + "結束.");
- }
- }
- Thread t = new Thread(r);
- t.start();
- }
- System.out.println(Thread.currentThread().getName() + "結束.");//列印結束標記
- long tEnd = System.currentTimeMillis();
- System.out.println("總共用時:"+ (tEnd - tStart) + "millions");
結果是幾乎在for迴圈結束的瞬間就執行了主線程列印總共用時的語句,原因是所有的子線程是並發執行的,它們運行時主線程也在運行,這就引出了一個問題即本文標題如何"讓主線程等待所有子線程執行完畢"。試過在每個子線程開始後加上t.join(),結果是所有線程都順序執行,這就失去了並發的意義了,顯然不是我想要的。
網上Google了很久也沒有找到解決方案,難道就沒有人遇到過這種需求嗎?還是這個問題太簡單了?無耐只得自己想辦法了...
最後我的解決辦法是,自訂一個ImportThread類繼承自java.lang.Thread,重載run()方法,用一個List屬性儲存所有產生的線程,這樣只要判斷這個List是否為空白就知道還有沒有子線程沒有執行完了,類代碼如下:
Java代碼
- 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);//線程結束時取消註冊
- System.out.println(Thread.currentThread().getName() + "結束.");//列印結束標記
- }
- 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是否為空白就能知道是否還有線程未執行完
- }
- }
主線程中代碼:
Java代碼
- 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){//等待所有子線程執行完
- if(!ImportThread.hasThreadRunning()){
- break;
- }
- Thread.sleep(500);
- }
- System.out.println(Thread.currentThread().getName() + "結束.");//列印結束標記
- long tEnd = System.currentTimeMillis();
- System.out.println("總共用時:"+ (tEnd - tStart) + "millions");
列印的結果是:
main開始
Thread-1開始...
Thread-5開始...
Thread-0開始...
Thread-2開始...
Thread-3開始...
Thread-4開始...
Thread-5結束.
Thread-4結束.
Thread-2結束.
Thread-0結束.
Thread-3結束.
Thread-1結束.
main結束.
總共用時:20860millions
可以看到main線程是等所有子線程全部執行完後才開始執行的。
==================================================以下為第二次編輯===============================================
上面的方法有一個隱患:如果線程1開始並且結束了,而其他線程還沒有開始此時runningThreads的size也為0,主線程會以為所有線程都執行完了。解決辦法是用一個非簡單類型的計數器來取代List型的runningThreads,並且線上程建立之前就應該設定好計數器的值。
MyCountDown類
Java代碼
- 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類
Java代碼
- 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() + "結束. 還有" + c.getCount() + " 個線程");//列印結束標記
- }
- }
主線程中
Java代碼
- 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){//等待所有子線程執行完
- if(!c.hasNext()) break;
- }
- System.out.println(Thread.currentThread().getName() + "結束.");//列印結束標記
列印結果:
main開始
Thread-2開始...
Thread-1開始...
Thread-0開始...
Thread-3開始...
Thread-5開始...
Thread-4開始...
Thread-5結束. 還有5 個線程
Thread-1結束. 還有4 個線程
Thread-4結束. 還有3 個線程
Thread-2結束. 還有2 個線程
Thread-3結束. 還有1 個線程
Thread-0結束. 還有0 個線程
main結束.
更簡單的方法:使用java.util.concurrent.CountDownLatch代替MyCountDown,用await()方法代替while(true){...}
ImportThread類
Java代碼
- 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();//線程結束時計數器減1
- System.out.println(Thread.currentThread().getName() + "結束. 還有" + threadsSignal.getCount() + " 個線程");
- }
- }
主線程中
Java代碼
- 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();//等待所有子線程執行完
- System.out.println(Thread.currentThread().getName() + "結束.");//列印結束標記
列印結果:
main開始
Thread-1開始...
Thread-0開始...
Thread-2開始...
Thread-3開始...
Thread-4開始...
Thread-5開始...
Thread-0結束. 還有5 個線程
Thread-1結束. 還有4 個線程
Thread-4結束. 還有3 個線程
Thread-2結束. 還有2 個線程
Thread-5結束. 還有1 個線程
Thread-3結束. 還有0 個線程
main結束.
Java多線程--讓主線程等待所有子線程執行完畢