1. 程式人生 > >Java多執行緒核心技術(六)執行緒組與執行緒異常

Java多執行緒核心技術(六)執行緒組與執行緒異常

本文應注重掌握如下知識點:

  1. 執行緒組的使用
  2. 如何切換執行緒狀態
  3. SimpleDataFormat 類與多執行緒的解決辦法
  4. 如何處理執行緒的異常

1.執行緒的狀態

執行緒物件在不同執行時期有不同的狀態,狀態資訊就處於State列舉類中,如圖所示:

執行緒狀態

  1. 初始(NEW):新建立了一個執行緒物件,但還沒有呼叫start()方法。

  2. 執行(RUNNABLE):Java執行緒中將就緒(ready)和執行中(running)兩種狀態籠統的稱為“執行”。

    執行緒物件建立後,其他執行緒(比如main執行緒)呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的執行緒在獲得CPU時間片後變為執行中狀態(running)。

  3. 阻塞(BLOCKED):表示執行緒阻塞於鎖。

  4. 等待(WAITING):進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)。

  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。

  6. 終止(TERMINATED):表示該執行緒已經執行完畢。

呼叫與執行緒有關的方法是造成執行緒狀態改變的主要原因,其關係如圖所示:(圖片來源於網路)

2.執行緒組

可以把執行緒歸屬到某一個執行緒組中,執行緒組中可以有執行緒物件,也可以有執行緒組,組中還可以有執行緒。

下面看下執行緒組的使用示例:

public class Group implements Runnable {

    public static void main(String[] args) {
        Group runnable = new Group();
        ThreadGroup threadGroup = new ThreadGroup("我的執行緒組");
        Thread threadA = new Thread(threadGroup,runnable);
        Thread threadB = new Thread(threadGroup,runnable);
        threadA.start();
        threadB.start();
        System.out.println("活動的執行緒"+threadGroup.activeCount());
        System.out.println("執行緒組的名稱"+threadGroup.getName());
    }

    @Override
    public void run() {
        while (true){
            System.out.println("Thread-Name: "+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果:

活動的執行緒2
執行緒組的名稱我的執行緒組
Thread-Name: Thread-0
Thread-Name: Thread-1

控制檯中的資訊表明有2個執行緒在名為“我的執行緒組”下活動。

上面的執行緒樹結構圖中顯示,所有的執行緒與執行緒組都在系統執行緒組下,下面演示如何建立具有多級關聯關係的執行緒結構,並獲取相關執行緒資訊。

示例程式碼:

public class Group implements Runnable {

    public static void main(String[] args) {
        ThreadGroup threadGroupMain = Thread.currentThread().getThreadGroup();
        Group runnable = new Group();
        ThreadGroup threadGroup = new ThreadGroup(threadGroupMain,"我的執行緒組");
        Thread threadA = new Thread(threadGroup,runnable);
        Thread threadB = new Thread(threadGroup,runnable);
        threadA.start();
        threadB.start();
        System.out.println("系統執行緒組的名字:"+Thread.currentThread().getThreadGroup().getName());
        System.out.println("系統執行緒組中有多少子執行緒:"+Thread.currentThread().getThreadGroup().activeCount());
        Thread[] threads1 = new Thread[threadGroupMain.activeCount()];
        threadGroupMain.enumerate(threads1);
        System.out.println("這些子執行緒具體是:");
        for (int i = 0; i < threads1.length; i++) {
            System.out.println(threads1[i].getName());
        }
        System.out.println("系統執行緒組中有多少子執行緒組:"+Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup[] listGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
        //enumerate方法將子執行緒組以複製的形式拷貝到陣列中,並返回拷貝的數量
        Thread.currentThread().getThreadGroup().enumerate(listGroup);
        System.out.println("子執行緒組的名字是:"+listGroup[0].getName());
        Thread[] threads = new Thread[listGroup[0].activeCount()];
        listGroup[0].enumerate(threads);
        System.out.println("子執行緒組中的執行緒有:");
        for (int i = 0; i < threads.length; i++) {
            System.out.println(threads[i].getName());
        }
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果:

系統執行緒組的名字:main
系統執行緒組中有多少子執行緒:4
這些子執行緒具體是:
main
Monitor Ctrl-Break
Thread-0
Thread-1
系統執行緒組中有多少子執行緒組:1
子執行緒組的名字是:我的執行緒組
子執行緒組中的執行緒有:
Thread-0
Thread-1

需要說明的是,在例項化一個ThreadGroup執行緒組x時如果不指定所屬的執行緒組,則x執行緒組自動歸屬到當前執行緒物件所屬的執行緒組中,也就是隱式的當前執行緒組中添加了一個子執行緒組。

執行緒組批量操作

通過將執行緒歸屬到執行緒組中,當呼叫執行緒組ThreadGroup的interrupt()方法時,可以將該組中的所有正在執行的執行緒批量停止。

3.SimpleDataFormat非執行緒安全

SimpleDataFormat主要負責日期的轉換與格式化,但在多執行緒的環境中,使用此類容易造成資料及處理的不準確,因為SimpleDataFormat並不是安全的。

解決方法有兩種,一是每個執行緒都new一個新的SimpleDataFormat例項物件;二是利用ThreadLocal類將SimpleDataFormat物件繫結到執行緒上。

4.執行緒中出現異常的處理

在 Java 的多執行緒技術中,可以對多執行緒中的異常進行“捕捉”,使用的是 UncaughtExceptionHandler類,從而可以對發生的異常進行有效的處理。

thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
     @Override
     public void uncaughtException(Thread t, Throwable e) {
          System.out.println("執行緒"+t.getName()+"出現了異常");
          e.printStackTrace();
     }
});

方法setUncaughtExceptionHandler()是給指定的執行緒物件設定的異常處理器。在Thread類中還可以使用setDefaultUncaughtExceptionHandler()方法對所有執行緒物件設定異常處理器。示例程式碼如下:

MyThread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
       @Override
       public void uncaughtException(Thread t, Throwable e) {
           System.out.println("執行緒"+t.getName()+"出現了異常");
           e.printStackTrace();
       }
});

方法setDefaultUncaughtExceptionHandler()的作用是為指定的執行緒類的所有執行緒物件設定預設的異常處理器。

5.執行緒組中出現異常的處理

使用重寫uncaughtException方法處理組內執行緒的異常時,每個執行緒內部不要有異常catch語句,如果有catch語句,則public void uncaughtException(Thread t, Throwable e) 方法不執行。

ThreadGroup group = new ThreadGroup(""){
      @Override
      public void uncaughtException(Thread t, Throwable e) {
             super.uncaughtException(t, e);
             //一個執行緒出現異常,中斷組內所有執行緒
             this.interrupt();
      }
};

前面介紹了執行緒物件的異常處理,執行緒類的異常處理,執行緒組的異常處理。將它們放一起會出現什麼效果呢?

示例程式碼:

public class MyThread{

    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("ThreadGroup"){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("執行緒組的異常處理");
                super.uncaughtException(t, e);
            }
        };
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("執行緒類的異常處理");
            }
        });
        Thread thread = new Thread(threadGroup,"Thread"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"執行");
                int i= 2/0;
            }
        };
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("執行緒物件的異常處理");
            }
        });
        thread.start();
    }

}

執行結果:

Thread執行
執行緒物件的異常處理

註釋掉執行緒物件的異常處理之後,再次執行:

public class MyThread{

    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("ThreadGroup"){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("執行緒組的異常處理");
                super.uncaughtException(t, e);
            }
        };
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("執行緒類的異常處理");
            }
        });
        Thread thread = new Thread(threadGroup,"Thread"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"執行");
                int i= 2/0;
            }
        };
//        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
//            @Override
//            public void uncaughtException(Thread t, Throwable e) {
//                System.out.println("執行緒物件的異常處理");
//            }
//        });
        thread.start();
    }

}

執行結果:

Thread執行
執行緒組的異常處理
執行緒類的異常處理

6.文末總結

本文彌補了前面幾個文章的技術空白點。到此,Java多執行緒程式設計核心技術的學習告一段落。

參考

《Java多執行緒程式設計核心技術》高洪巖著

擴充套件