在學習多線程時,最開始遇到的問題實際上是「計算子線程運行時間」,寫到最後發現本文和標題更爲符合,可是仍然基於問題:「在主線程中獲取子線程的運行時間」。html
對於「主線程如何獲取子線程總運行時間」的問題,最開始想到的是使用while循環進行輪詢:java
Thread t = new Thread(() -> { //子線程進行字符串鏈接操做 int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); }); //開始計時 long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); long end = 0; while(t.isAlive() == true){//t.getState() != State.TERMINATED這兩種判斷方式均可以 end = System.currentTimeMillis(); } System.out.println("end = " + end); System.out.println("end - start = " + (end - start));
可是這樣太消耗CPU,因此我在while循環里加入了暫停:segmentfault
while(t.isAlive() == true){ end = System.currentTimeMillis(); try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } }
這樣作的結果雖然cpu消耗減小,可是數據不許確了api
接着我又找到了第二種方法:數組
long start = System.currentTimeMillis(); System.out.println("start = " + start); t1.start(); try { t.join();//注意這裏 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - Start:" + (end - start));
使用join()方法,join()方法的做用,是等待這個線程結束;(t.join()方法阻塞調用此方法的線程(calling thread),直到線程t完成,此線程再繼續,這裏貼個說的挺清楚的博客)安全
第二種方法的確實現了計時,接着我又想到了多線程的等待喚醒機制,思路是:子線程啓動後主線程等待,子線程結束後喚醒主線程。因而有了下面的代碼:多線程
Object lock = new Object(); Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); lock.notify();//子線程喚醒 }); //計時 long start = System.currentTimeMillis(); System.out.println("start = " + start); //啓動子線程 t.start(); try { lock.wait();//主線程等待 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start));
可是這樣會拋出兩個異常:
因爲對wait()和notify()的理解並非很深入,因此我最開始並不清楚爲何會出現這樣的結果,由於從報錯順序來看子線程並無提早喚醒,因而我在segmentfault和CSDN都發出了提問,同時也詢問了我一個很厲害的朋友,最後得出的結論是調用wait()方法時須要獲取該對象的鎖,Object文檔裏是這麼說的:oracle
The current thread must own this object's monitor.
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.學習
因此上面的代碼須要改爲這樣:this
Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); synchronized (lock) {//獲取對象鎖 lock.notify();//子線程喚醒 } }); //計時 long start = System.currentTimeMillis(); System.out.println("start = " + start); //啓動子線程 t.start(); try { synchronized (lock) {//這裏也是同樣 lock.wait();//主線程等待 } } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start));
這樣的確得出告終果,可是我想知道兩個線程的執行順序,因而在wait和nitify先後分別加了一個輸出,最後得出的運行結果是:
能夠看出主線程先wait子線程再notify,也就是說,若是子線程在主線程wati前調用了nitify,會致使主線程無限等待,因此這個思路仍是有必定的漏洞的。
關於wait和notify這裏貼個挺清楚的博客
第四種方式能夠等待多個線程結束,就是使用java.util.concurrent包下的CountDownLatch類(關於CountDownLatch的用法能夠參考這篇簡潔的博客)
簡單來講,CountDownLatch類是一個計數器,能夠設置初始線程數(設置後不能改變),在子線程結束時調用countDown()方法可使線程數減一,最終爲0的時候,調用CountDownLatch的成員方法wait()的線程就會取消BLOKED阻塞狀態,進入RUNNABLE從而繼續執行。下面上代碼:
int threadNumber = 1; final CountDownLatch cdl = new CountDownLatch(threadNumber);//參數爲線程個數 Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); cdl.countDown();//此方法是CountDownLatch的線程數-1 }); long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); //線程啓動後調用countDownLatch方法 try { cdl.await();//須要捕獲異常,當其中線程數爲0時這裏纔會繼續運行 }catch (InterruptedException e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start));
又想到剛學習了線程池,線程池的submit()的返回對象Future接口有一個get()方法也能夠阻塞當前線程(其實該方法主要用途是獲取子線程的返回值),因此第五種方法也出來了:
ExecutorService executorService = Executors.newFixedThreadPool(1); Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); }); long start = System.currentTimeMillis(); System.out.println("start = " + start); Future future = executorService.submit(t);//子線程啓動 try { future.get();//須要捕獲兩種異常 }catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); executorService.shutdown();
這裏, ThreadPoolExecutor 是實現了 ExecutorService的方法, sumbit的過程就是把一個Runnable接口對象包裝成一個 Callable接口對象, 而後放到 workQueue裏等待調度執行. 固然, 執行的啓動也是調用了thread的start來作到的, 只不過這裏被包裝掉了. 另外, 這裏的thread是會被重複利用的, 因此這裏要退出主線程, 須要執行如下shutdown方法以示退出使用線程池. 扯遠了.
這種方法是得益於Callable接口和Future模式, 調用future接口的get方法, 會同步等待該future執行結束, 而後獲取到結果. Callbale接口的接口方法是 V call(); 是能夠有返回結果的, 而Runnable的 void run(), 是沒有返回結果的. 因此, 這裏即便被包裝成Callbale接口, future.get返回的結果也是null的.若是須要獲得返回結果, 建議使用Callable接口.參見這篇博客
看到這個Callable忽然想到以前看C#多線程的時候有說到回調的問題,所以先開個坑,下篇博文說說Java的Callable與callback問題,先貼個Callable的簡單講解
同時,在concurrent包中,還提供了BlockingQueue(隊列)來操做線程,BlockingQueue的主要的用法是在線程間安全有效的傳遞數據,具體用法能夠參見這篇博客,對於BlockingQueue說的很是詳細。所以,第六種方法也出來了:
BlockingQueue queue = new ArrayBlockingQueue(1);//數組型隊列,長度爲1 Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); try { queue.put("OK");//在隊列中加入數據 } catch (InterruptedException e) { e.printStackTrace(); } }); long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); try { queue.take();//主線程在隊列中獲取數據,take()方法會阻塞隊列,ps還有不會阻塞的方法 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start));
那麼,有沒有第七種方式呢?固然有啦~,仍是concurrent包,只不過此次試用CyclicBarrier類:
CyclicBarrier字面意思迴環柵欄,經過它能夠實現讓一組線程等待至某個狀態以後再所有同時執行。叫作迴環是由於當全部等待線程都被釋放之後,CyclicBarrier能夠被重用。
CyclicBarrier barrier = new CyclicBarrier(2);//參數爲線程數 Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); try { barrier.await();//阻塞 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); try { barrier.await();//也阻塞,而且當阻塞數量達到指定數目時同時釋放 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start));
實際是上面這種方法是不太嚴謹的,由於在子線程阻塞以後若是還有代碼是會繼續執行的,固然本例中後面是沒有代碼可執行了,能夠近似理解爲是子線程的運行時間。
這裏貼個CountDownLatch、CyclicBarrier和Semaphore的講解博客
至此,集齊了七顆龍珠,得出小結: