【Java並發系列01】Thread及ThreadGroup雜談


一、前言

  最近開始學習Java並發編程,把學習過程記錄下。估計不是那么系統,主要應該是Java API的介紹(不涉及最基礎的概念介紹),想要深入系統學習推薦看一本書《Java Concurrency in Practice 》(建議看英文,也可以看中文譯本:《 Java 並發編程實戰》)。

  並發編程的基礎就是線程,所以這一篇對線程做初步了解。

二、Thread和ThredGroup的關系

  因為Thread的構造函數中有關於ThradGroup的,所以了解它們之間的關系是有必要的。ThradGroup之間的關系是樹的關系而Thread與ThradGroup的關系就像元素與集合的關系。關系圖簡單如下:

  圖片引用突破渴望的博客:http://www.cnblogs.com/hvicen/p/6218981.html,謝謝指正錯誤。

  其中有一點要明確一下:main方法執行后,將自動創建system線程組合main線程組,main方法所在線程存放在main線程組中。

三、Thread API

3.1 基本屬性

  首先應該了解線程的基本屬性:

  • name:線程名稱,可以重復,若沒有指定會自動生成。
  • id:線程ID,一個正long值,創建線程時指定,終生不變,線程終結時ID可以復用。
  • priority:線程優先級,取值為1到10,線程優先級越高,執行的可能越大,若運行環境不支持優先級分10級,如只支持5級,那么設置5和設置6有可能是一樣的。
  • state:線程狀態,Thread.State枚舉類型,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 5種。
  • ThreadGroup:所屬線程組,一個線程必然有所屬線程組
  • UncaughtExceptionHandler:未捕獲異常時的處理器,默認沒有,線程出現錯誤后會立即終止當前線程運行,並打印錯誤。

3.2 字段摘要

  Thread類有三個字段,設置線程優先級時可使用:

  • MIN_PRIORITY:1,最低優先級
  • NORM_PRIORITY:5,普通優先級
  • MAX_PRIORITY:10,最高優先級

3.3 構造方法

  現只介紹參數最多的一個:

  Thread(ThreadGroup group, Runnable target, String name, long stackSize) 

  • group:指定當前線程的線程組,未指定時線程組為創建該線程所屬的線程組。
  • target:指定運行其中的Runnable,一般都需要指定,不指定的線程沒有意義,或者可以通過創建Thread的子類並重新run方法。
  • name:線程的名稱,不指定自動生成。
  • stackSize:預期堆棧大小,不指定默認為0,0代表忽略這個屬性。與平台相關,不建議使用該屬性

3.4 方法摘要

3.4.1 靜態方法

  • Thread Thread.currentThread() :獲得當前線程的引用。獲得當前線程后對其進行操作。
  • Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回線程由於未捕獲到異常而突然終止時調用的默認處理程序。
  • int Thread.activeCount():當前線程所在線程組中活動線程的數目。
  • void dumpStack() :將當前線程的堆棧跟蹤打印至標准錯誤流。
  • int enumerate(Thread[] tarray) :將當前線程的線程組及其子組中的每一個活動線程復制到指定的數組中。
  • Map<Thread,StackTraceElement[]> getAllStackTraces() :返回所有活動線程的堆棧跟蹤的一個映射。
  • boolean holdsLock(Object obj) :當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true。
  • boolean interrupted() :測試當前線程是否已經中斷。
  • void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :設置當線程由於未捕獲到異常而突然終止,並且沒有為該線程定義其他處理程序時所調用的默認處理程序。
  • void sleep(long millis) :休眠指定時間
  • void sleep(long millis, int nanos) :休眠指定時間
  • void yield() :暫停當前正在執行的線程對象,並執行其他線程。意義不太大

3.4.2 普通方法

  • void checkAccess() :判定當前運行的線程是否有權修改該線程。
  • ClassLoader getContextClassLoader() :返回該線程的上下文 ClassLoader。
  • long getId() :返回該線程的標識符。
  • String getName() :返回該線程的名稱。
  • int getPriority() :返回線程的優先級。
  • StackTraceElement[] getStackTrace() :返回一個表示該線程堆棧轉儲的堆棧跟蹤元素數組。
  • Thread.State getState() :返回該線程的狀態。
  • ThreadGroup getThreadGroup() :返回該線程所屬的線程組。
  • Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回該線程由於未捕獲到異常而突然終止時調用的處理程序。
  • void interrupt() :中斷線程。
  • boolean isAlive() :測試線程是否處於活動狀態。
  • boolean isDaemon() :測試該線程是否為守護線程。
  • boolean isInterrupted():測試線程是否已經中斷。
  • void join() :等待該線程終止。
  • void join(long millis) :等待該線程終止的時間最長為 millis 毫秒。
  • void join(long millis, int nanos) :等待該線程終止的時間最長為 millis 毫秒 + nanos 納秒。
  • void run() :線程啟動后執行的方法。
  • void setContextClassLoader(ClassLoader cl) :設置該線程的上下文 ClassLoader。
  • void setDaemon(boolean on) :將該線程標記為守護線程或用戶線程。
  • void start():使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
  • String toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。

3.4.3 作廢方法

  • int countStackFrames() :沒有意義不做解釋。
  • void destroy() :破壞線程,不釋放鎖,已經不能再使用,使用會拋出NoSuchMethodError。 
  • void suspend() :掛起線程,不要使用。
  • void resume() :恢復線程,不要使用。
  • void stop() :停止線程釋放鎖,不要使用。
  • void stop(Throwable obj) :同上。

3.5 Thread知識方法講解

3.5.1 setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 

  首先要了解什么是Thread.UncaughtExceptionHandler,默認來說當線程出現未捕獲的異常時,會中斷並拋出異常,拋出后的動作只有簡單的堆棧輸出。如:

public class ThreadTest{
    public static void main(String[] args) throws Exception{
        Thread t1=new Thread(new Runnable(){
            public void run(){
                int a=1/0;
            }
        });
        t1.start();
    }
}

  那么代碼運行到int a=1/0;就會報錯:

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at yiwangzhibujian.ThreadTest$1.run(ThreadTest.java:11)
    at java.lang.Thread.run(Thread.java:662)

  這時候如果設置了Thread.UncaughtExceptionHandler,那么處理器會將異常進行捕獲,捕獲后就可以對其進行處理:

public class ThreadTest{
    public static void main(String[] args) throws Exception{
        Thread t1=new Thread(new Runnable(){
            public void run(){
                int a=1/0;
            }
        });
        t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
            @Override
            public void uncaughtException(Thread t,Throwable e){
                System.out.println("線程:"+t.getName()+"出現異常,異常信息:"+e);
            }
        });
        t1.start();
    }
}

  那么當線程拋出異常后就可以對其抓取並進行處理,最終結果如下:

線程:Thread-0出現異常,異常信息:java.lang.ArithmeticException: / by zero

  如果自己寫線程,那么完全可以在run方法內,將所有代碼進行try catch,在catch里做相同的操作。UncaughtExceptionHandler的意義在於不對(或者不能對)原有線程進行修改的情況下,為其增加一個錯誤處理器。

3.5.2 interrupt() 、interrupted() 、isInterrupted()作用

  因為stop()方法已經不建議使用了,下面的3.5.4進行詳解,所以如何中斷一個線程就成了一個問題,一種簡單的辦法是設置一個全局變量needStop,如下:

@Override
public void run(){
    while(!needStop){
        //執行某些任務
    }
}

  或者需要操作耗時較長的方法內,每一步執行之前進行判斷:

@Override
public void run(){
    //耗時較長步驟1
    if(needStop) return;
    //耗時較長步驟2
    if(needStop) return;
    //耗時較長步驟3
}

  這樣在其他的地方將此線程停止掉,因為停止是在自己的預料下,所以不會有死鎖或者數據異常問題(當然你的程序編寫的時候要注意)。

  其實Thread類早就有類似的功能,那就是Thread具有中斷屬性。可以通過調用interrupt()方法對線程中斷屬性設置為true,這將導致如下兩種情況:

  • 當線程正常運行時,中斷屬性設置為true,調用其isInterrupted()方法會返回true。
  • 當線程阻塞時(wait,join,sleep方法),會立即拋出InterruptedException異常,並將中斷屬性設置為false。此時再調用isInterrupted()會返回false。

  這樣就由程序來決定當檢測到中斷屬性為true時,怎么對線程中斷進行處理。因此,上面的代碼可以改成:

@Override
public void run(){
    while(!Thread.currentThread().isInterrupted()){
        //執行某些任務
    }
}
---------------------------------------------------------
@Override
public void run(){
    //耗時較長步驟1
    if(Thread.currentThread().isInterrupted()) return;
    //耗時較長步驟2
    if(Thread.currentThread().isInterrupted()) return;
    //耗時較長步驟3
}

  interrupted()的方法名容易給人一種誤解,看似和interrupt()方法一樣,但是其實際含義是,返回當前中斷狀態,並將其設置為false。

3.5.3 yield()和sleep(0)

  yield()方法的API容易給人一種誤解,它的實際含義是停止執行當前線程(立即),讓CPU重新選擇需要執行的線程,因為具有隨機性,所以也有可能重新執行該線程,通過下面例子了解:

public class ThreadTest{
    public static void main(String[] args) throws Exception{
        Thread t1=new Thread(new Runnable(){
            @Override
            public void run(){
                while(true){
                    System.out.println(1);
                    Thread.yield();
                }
            }
        });
        Thread t2=new Thread(new Runnable(){
            public void run(){
                while(true){
                    System.out.println(2);
                    Thread.yield();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

  程序執行結果並不是121212而是有,有連續的1和連續的2。

  經過測試yield()和sleep(0)的效果是一樣的,sleep(0)底層要么是和yield()一樣,要么被過濾掉了(純靠猜測),不過sleep(0)沒有任何意義。要是真打算讓當前線程暫停還是應該使用sleep(long millis,int nanos)這個方法,設置幾納秒表示下誠意,或者找到想要讓步的線程,調用它的join方法更實際一些。

3.5.4 stop()、suspend()、resume()為什么不建議使用

  stop方法會立即中斷線程,雖然會釋放持有的鎖,但是線程的運行到哪是未知的,假如在具有上下文語義的位置中斷了,那么將會導致信息出現錯誤,比如:

@Override
public void run(){
    try{
        //處理資源並插入數據庫
    }catch(Exception e){
        //出現異常回滾
    }
}

  如果在調用stop時,代碼運行到捕獲異常需要回滾的地方,那么將會因為沒有回滾,保存了錯誤的信息。

  而suspend會將當前線程掛起,但是並不會釋放所持有的資源,如果恢復線程在調用resume也需要那個資源,那么就會形成死鎖。當然可以通過你精湛的編程來避免死鎖,但是這個方法具有固有的死鎖傾向。所以不建議使用。其他暫停方法為什么可用:

  • wait方法會釋放鎖,所以不會有死鎖問題
  • sleep方法雖然不釋放鎖,但是它不需要喚醒,在使用的時候已經指定想要的睡眠時間了。

  jdk的文章詳細介紹了方法禁用的原因:文章地址,有空可以看一看,如果你足夠大膽,也是可以使用的。

四、ThreadGroup API

4.1 基本屬性

  name:當前線程的名稱。

  parent:當前線程組的父線程組。

  MaxPriority:當前線程組的最高優先級,其中的線程優先級不能高於此。

4.2 構造方法

  只介紹一個構造方法:

  ThreadGroup(ThreadGroup parent, String name) :

  • parent:父線程組,若為指定則是創建該線程組的線程所需的線程組。
  • name:線程組的名稱,可重復。

4.3 常用方法摘要

  API詳解(中文,英文)。

  • int activeCount():返回此線程組中活動線程的估計數。 
  • void interrupt():中斷此線程組中的所有線程。 
  • void uncaughtException(Thread t, Throwable e) :設置當前線程組的異常處理器(只對沒有異常處理器的線程有效)。

4.4 ThreadGroup作用

  這個線程組可以用來管理一組線程,通過activeCount() 來查看活動線程的數量。其他沒有什么大的用處。 

 

  這篇博客講解了最基本的線程及線程組,這是並發編程的根基,應該全面了解。

  未經許可禁止轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM