• 线程组和未处理的异常


    Java 使用 ThreadGroup 来表示线程组,它可以对一批线程进行分类管理,Java 允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程。用户创建的所有线程都属于指定线程组,如果程序没有显式指定线程属于哪个线程组,则该线程属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一个线程组内,例如A线程创建了B线程,并且没有指定B线程的线程组,则B线程属于A线程所在的线程组。

    一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组。

    Thread 类提供了如下几个构造器来设置新创建的线程属于哪个线程组。

    • Thread(ThreadGroup group, Runnable target):以 target 的 run() 方法作为线程执行体创建新线程,属于 group 线程组。
    • Thread(ThreadGroup group, Runnable target, String name):以 target 的 run() 方法作为线程执行体创建新线程,该线程属于 group 线程组,且线程名为 name。
    • Thread(ThreadGroup group, String name):创建新线程,新线程名为 name,属于 group 线程组。

    因为中途不可改变线程所属的线程组,所以 Thread 类没有提供 setThreadGroup() 方法来改变线程所属的线程组(但提供了一个 getThreadGroup() 方法来返回该线程所属的线程组,getThreadGroup() 方法的返回值是 ThreadGroup 对象,表示一个线程组。ThreadGroup 类提供了如下两个简单的构造器来创建实例。

    • ThreadGroup(String name):以指定的线程组名字来创建新的线程组。
    • ThreadGroup(ThreadGroup parent, String name):以指定的名字、指定的父线程组创建一个新线程组。

    上面两个构造器在创建线程组实例时都必须为指定一个名字。也就是说,线程组总会具有一个字符串类型的名字,该名字可通过调用ThreadGroup的 getName() 方法来获取,但不允许改变线程组的名字。 ThreadGroup 类提供了如下几个常用的方法来操作整个线程组的所有线程。

    • int activeCount():返回此线程组中活动线程的数目。
    • interrupt():中断此线程组中所有的线程。
    • isDaemon():判断该线程组是否是后台线程组。
    • setDaemon(boolean daemon):把该线程组设置成后台线程组。后台线程组具有一个特征——当后台线程组的最后一个线程执行结束或最后一个线程被销毁后,后台线程组将自动销毁。
    • setMaxPriority(int pri):设置线程组的最高优先级。

    下面程序创建几个线程,它们分别属于不同的线程组,程序还将一个线程组设置成后台线程。

    class TestThread extends Thread{
        //提供指定线程名的构造器
        public TestThread(String name){
            super(name);
        }
        //提供指定线程名、线程组的构造器
        public TestThread(ThreadGroup group , String name){
            super(group, name);
        }
        public void run(){
            for (int i = 0; i < 20 ; i++ ){
                System.out.println(getName() + " 线程的i变量" + i);
            }
        }
    }
    public class ThreadGroupTest{
        public static void main(String[] args) {
            //获取主线程所在的线程组,这是所有线程默认的线程组
            ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
           System.out.println("主线程组的名字:" + mainGroup.getName());
           System.out.println("主线程组是否是后台线程组:" + mainGroup.isDaemon());
    
           new TestThread("主线程组的线程").start();
           ThreadGroup tg = new ThreadGroup("新线程组");
           tg.setDaemon(true);
           System.out.println("tg线程组是否是后台线程组:" + tg.isDaemon());
           TestThread tt = new TestThread(tg , "tg组的线程甲");
           tt.start();
           new TestThread(tg , "tg组的线程乙").start();
        }
    }

    上面程序中的第一段粗体字代码用于获取主线程所属的线程组,并访问该线程组的相关属性,第二段粗体字代码创建了一个新线程组,并将该线程组设置成后台线程。

    ThreadGroup 内还定义了一个很有用的方法:void uncaughtException(Thread t , Throwable e),该方法可以处理该线程组内任意线程所抛出的未处理异常。

    从 Java 5 开始, Java 加强了线程的异常处理,如果线程执行过程中抛出了一个未处理异常, JVM 在结束该线程之前会自动查找是否有对应的 Thread.UncaughtExceptionHandler 对象,如果找到该处理器对象,则会调用该对象的 uncaughtException(Thread t, Throwable e) 方法来处理该异常。

    Thread.UncaughtExceptionHandler 是 Thread 类的一个静态内部接口,该接口内只有一个方法: void uncaughtException(Thread t , Throwable e),该方法中的 t 代表出现异常的线程,而 e 代表该线程抛出的异常。

    Thread 类提供了如下两个方法来设置异常处理器。

    • static setDefaultUncaughtExceptionHandler (Thread.UncaughtExceptionHandler eh):为该线程类的所有线程实例设置默认的异常处理器。
    • setUncaughtExceptionHandler (Thread.UncaughtExceptionHandler  eh):为指定的线程实例设置异常处理器。

    ThreadGroup 类实现了 Thread.UncaughtExceptionHandler 接口,所以每个线程所属的线程组将会作为默认的异常处理器。当一个线程抛出未处理异常时, JVM 会首先查找该异常对应的异常处理器(setUncaughtExceptionHandler() 方法设置的异常处理器),如果找到该异常处理器,则将调用该异常处理器处理该异常;否则,JVM 将会调用该线程所属的线程组对象的 uncaughtException() 方法来处理该异常。

    线程组处理异常的默认流程如下。

    1. 如果该线程组有父线程组,则调用父线程组的 uncaughtException() 方法来处理该异常。
    2. 如果该线程实例所属的线程类有默认的异常处理器(由 setDefaultUncaughtExceptionHandler() 方法设置的异常处理器),那么就调用该异常处理器来处理该异常。
    3. 如果该异常对象是 ThreadDeath 的对象,则不做任何处理;否则,将异常跟踪栈的信息打印到 System.err 错误输出流,并结束该线程。

    下面程序为主线程设置了异常处理器,当主线程运行抛出未处理异常时,该异常处理器将会起作用。

    class MyExHandler implements Thread.UncaughtExceptionHandler{
        //实现uncaughtException方法,该方法将处理线程的未处理异常
        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

    从上面程序的执行结果来看,虽然程序中粗体字代码指定了异常处理器对未捕获的异常进行处理,而且该异常处理器也确实起作用了,但程序依然不会正常结束。这说明异常处理器与通过 catch 捕获异常是不同的——当使用 catch 捕获异常时,异常不会向上传播给上一级调用者;但使用异常处理器对异常进行处理之后,异常依然会传播给上一级调用者。

  • 相关阅读:
    Individual Method Selection Survey rubric
    Xcode 出现Thread 1: signal SIGABRT
    C/C++生成随机数
    《深入浅出深度学习:原理剖析与python实践》第八章前馈神经网络(笔记)
    操作系统--精髓与设计原理(第八版)第三章复习题答案
    操作系统--精髓与设计原理(第八版)第二章复习题答案
    Python知识点整理
    C++ <queue>用法
    C语言结构体用法
    Mac安装Qt出现错误Could not resolve SDK Path for 'macosx'
  • 原文地址:https://www.cnblogs.com/jwen1994/p/10632372.html
Copyright © 2020-2023  润新知