多線程三(線程組和線程池)
線程組和線程池
一. 線程組
1. 線程組介紹及使用
Java使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許直接對線程組進行控制。對線程組的控制相當於控制這批線程。
在默認情況下,子線程和創建它的父線程同屬於一個線程組。
一旦線程假如某個線程組之後,該線程將一直屬於該線程組,知道該線程死亡,線程運行途中不能改變它所屬的線程組。
Thread提供了不同構造器設置新創建的線程屬於哪個線程組。提供getThreadGroup()方法返回該線程所屬的線程組對象。
ThreadGroup類提供了如下兩個構造器創建實例。
- ThreadGroup(String name):以指定的線程組名字來創建新的線程組
- ThreadGroup(ThreadGroup parent,String name):以指定的名字、指定的父線程組創建一個新線程組
Java程序不允許改線程組名字,通過getName()方法獲取線程組名字。
ThreadGroup類提供了如下常用的方法
- int activeCount():返回此線程組中活動的線程數目
- interrupt():中斷此線程組中的所有線程
- isDaemon():判斷該線程組是否是後臺線程組
- setDaemon(boolean daemon):把該線程組設置成後臺線程組。
- setMaxPriority(int pri):設置線程組的最高優先級。
2.線程組和異常處理機制
從Java 5開始,Java加強了線程的異常處理,如果線程執行過程中拋出了一個未處理異常,JVM在結束該線程之前會自動查找是否有對應的Thread.UncaughtExceptionHandler對象,如果找到該處理器對象,則會調用該對象的uncaughtException(Thread t,Throwable e)方法來處理該異常。
Thread類提供了兩個方法設置異常處理器。
- static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):為該線程類的所有線程實例設置默認的異常處理器
- setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):為指定的線程實例設置異常處理器
ThreadGroup類實現了Thread.UncaughtExceptionHandler接口,所以每個線程所屬的線程組將會作為默認的異常處理器。
如果線程執行過程中拋出了一個未處理異常,JVM在結束該線程之前會自動查找是否有對應的Thread.UncaughtExceptionHandler對象,如果找到該處理器對象,則會調用該對象的uncaughtException(Thread t,Throwable e)方法來處理該異常;否則,JVM會調用該線程所屬的線程組對象的uncaughtException()方法來處理該異常。
線程組處理異常的流程如下:
- 如果該線程組有父線程組,則調父線程組的uncaughtException()方法來處理該異常。
- 如果該線線程實例所屬的線程類有默認的異常處理器,那麽調用該異常處理器來處理異常
- 如果該對象是ThreadDeath對象,則不做任何處理;否則,將異常跟蹤棧的信息打印到System.err錯誤輸出流,並結束該線程。
下面主程序設置了異常處理器。
package com.gdut.thread; class MyExHandler implements Thread.UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t+"線程出現了異常"+e); } } public class ExHandler { public static void main(String[] args) { Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler()); int a=5/0; System.out.println("程序正常結束"); } }
輸出:Thread[main,5,main]線程出現了異常java.lang.ArithmeticException: / by zero
二. 線程池
系統啟動一個新線程的成本是非常高的,因為它涉及與操作系統交互。當程序中需要創建大量生存期很短暫的線程時,應該考慮使用線程池來提高系統性能。
與數據庫連接池類似的是,線程池在系統啟動時即創建大量空閑的線程,當序將一個Runnable對象或Callable對象創給線程池,線程池就會啟動一個線程來執行他們的run()或call()方法,當run()或call()方法執行結束後,該線程並不會死亡,而是再次返回線程池中稱為空閑狀態,等待執行下一個Runnable對象的run()或call()方法。
除此之外,使用線程池可以有效地控制系統中並發線程的數量,當系統中包含大量並發線程時,會導致系統性能劇烈下降,甚至導致JVM崩潰。
2.1 Java 8改進的線程池
2.2 Java 8增強的線池
為了充分利用多CPU的優勢、多核CPU的性能優勢。可以考多個小任務,把小任務放到多個處理器核心上並行執行;當多個小任務執行完成之後,再將這些執行結果合並起來即可。Java 7提供了ForkJoinPool來支持這個功能。
ForkJoinPool是ExecutorService的實現類,因此是一種特殊的線程池。提供了如下兩個常用的構造器
- ForkJoinPool(int parallelism):創建一個包含parallelism個並行線程的ForkJoinPool.
- ForkJoinPool():以Runtime.availableProssesors()方法的返回值作為paralelism參數來創建ForkJoinPool.
Java 8進一步拓展了ForkJoinPool的功能,Java 8增加了通用池功能。ForkJoinPool通過如下兩個方法提供通用池功能。
- ForkJoinPool commonPool():該方法返回一個通用池,通用池的狀態不會受shutdown()或shutdownNow()方法的影響。
- int getCommonPoolParallelism():該方法返回通用池的並行級別。
創建了通用池ForkJoinPool實例之後,就可調用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法來執行指定任務了。其中,ForkJoinTask代表一個並行,合並的任務。
ForkJoinTask是一個抽象類,它還有兩個抽象子類:RecursiveAction和recursiveTask。其中RecursiveAction代表沒有返回值的任務,RecursiveTask代表有返回值的任務。
下面程序將一個大任務(打印0~500)的數值分成多個小任務,並將任務交給ForkJoinPool來執行。
package com.gdut.thread.threadPool; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; import java.util.concurrent.TimeUnit; class PrintTask extends RecursiveAction{ private static final int THRESHOLD = 50; private int start; private int end; public PrintTask(int start,int end) { this.start = start; this.end = end; } @Override protected void compute() { if(end-start<THRESHOLD){ for (int i = start; i <end ; i++) { System.out.println(Thread.currentThread().getName()+"的i值"+i); } }else{ //當end與start的差大於THRESHOLD時,即要打印的數超過50時,將大任務分成兩個小任務 int middle = (end+start)/2; PrintTask left = new PrintTask(start,middle); PrintTask right = new PrintTask(middle,end); left.fork(); right.fork(); } } } public class ForkJoinPoolTest{ public static void main(String[] args) throws InterruptedException{ ForkJoinPool pool = new ForkJoinPool(); pool.submit(new PrintTask(0,500)); pool.awaitTermination(2, TimeUnit.SECONDS); pool.shutdown(); } }
8核計算機的執行效果
多線程三(線程組和線程池)