zoukankan      html  css  js  c++  java
  • 【转】线程、Thread类和线程终止

    一、线程Thread启动

    0. Thread类实现了java.lang.Runnable接口,即实现了run方法。虽然在Sun JDK中,start()调用了start0()方法,start0()方法又是native的,但实际上新的线程就是调用了Thread的run()方法,当然这native的实现中一定有线程的fork操作,使两个线程并列执行。

    1. Thread类有8个重载的构造方法。在Sun JDK的源码中,这8个构造方法都是调用了一个私有的init()方法来初始化对象的各个属性。这其中会将当前调用线程作为parent线程,确定线程组和安全权限。说一句,线程组主要是做安全方面考虑的,《Thinking in Java》这本书中表示线程组实际上是一个比较失败的东西,没有具体去提,本文也不会详细去说明。除此之外的代码逻辑如下,设置了各个Thread对象的属性。

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
     
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            //Determine if it's an applet or not
     
            // If there is a security manager, ask the security manager what to do.
            if (security != null) {
                g = security.getThreadGroup();
            }
     
            //If the security doesn't have a strong opinion of the matter use the parent thread group.
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
     
        // checkAccess regardless of whether or not threadgroup is explicitly passed in.
        g.checkAccess();
     
        // Do we have the required permissions?
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
     
        g.addUnstarted();
     
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        this.name = name.toCharArray();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext = AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        // Stash the specified stack size in case the VM cares
        this.stackSize = stackSize;
     
        // Set thread ID
        tid = nextThreadID();
    }

    其中target属性是Runnable对象,也是线程所要完成的执行任务内容,而deamon则是一个是否为守护线程的布尔值标记,可以看到默认是继承于当前调用线程的(parent),这个可以通过setDaemon来重新设置,上下文的类加载器contextClassLoader也是一样。

    2. 线程的运行启动。即调用start()方法,上面刚刚提到,实际会调用到run()方法。如果是Thread的子类,则可以通过重写run()方法来做任务实现。而在Thread默认的run()方法实现中,是这样子的:

    if (target != null) {
       target.run();
    }

    这个target前面提到过,就是初始化的Runnable任务对象。

    3. 线程状态。对于一个线程来讲,状态通常可以分为几大类NEW、RUNNING、BLOCKED、DEAD,当然Running可以分为真正执行和就绪,阻塞BLOCKED也有多种阻塞方式。在1.5之后的Sun JDK中,Thread内部是有一个State的枚举类的,但这个通常不是用来做开发,而是用于测试。通常来说,我们能做的就是用Thread对象的isAlive()方法判断,得到true或者false。

    二、线程Thread终止

    看下面代码,觉得会怎样?

    public class App {
        static class ExRun implements Runnable {
            public void run() {
                System.out.println("throwing...");
                throw new RuntimeException();
            }
        }
     
        public static void main(String[] args) {
            try {
                new Thread(new ExRun()).start();
            } catch (Exception e) {
                System.out.println("runtime ex. catched!");
            }
        }
    }

    会在控制台打出”runtime ex. catched!”?你确定么?不怕大家笑话,几年我第一眼看类似这段代码的时候,竟然也认为这很理所当然。其实,这也许不是年头的问题,是是否仔细思考和经验问题。只要仔细想一想,既然已经new了一个Thread并且让他start()去了,异常又不是在new的过程中或者start的过程中出来的,抛不抛异常关这个主线程什么事儿。

    那如果一个被主线程创建的新线程除了什么“故障”,就让其悄悄地自生自灭么?有没有补救方案呢?有的。在JavaSE5之前,这个问题直接交给线程组(ThreadGroup)了事儿,但在这之后就有了java.lang.Thread$UncaughtExceptionHandler($是内部关系,后者是前者的内部类/接口)这么一个interface,里面只有一个方法:

    void uncaughtException(Thread t, Throwable e);

    这个方法就是负责处理异常的,Thread参数不说了,那这个Throwable又是啥,这个就是Exception的老爸了,不了解的异常类层次结构的,这儿您就可以查查去了。既然是Throwable,其实也就不只是Exception了,Error也会被处理到。

    接着就简单说下怎么用,Thread类的两个方法:

    • public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
    • public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

    第一个是实例方法,需要针对某个Thread对象使用,而第二个是静态方法,设置全局的默认处理方法,但这两个不冲突,在Thread中是两个独立的handler属性。但毕竟默认的和非默认的还不一样,有个顺序问题,这里又不得不再提到线程组(ThreadGroup)

    在JavaSE5之前,没有UncaughtExceptionHandler这个东西,所以出错会直接交给Thread的ThreadGroup对象属性group处理,因为ThreadGroup有处理异常的方法,而UncaughtExceptionHandler出来后,ThreadGroup也实现了这个接口。按照文档注释说明:

    1. 优先检查线程的uncaughtExceptionHandler
    2. 否则交给group处理
    3. 最后才可能轮到defaultUncaughtExceptionHandler

    而看源码中线程组的处理实际上是追溯一个parent(也是ThreadGroup类对象)链:

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread ""
                                 + t.getName() + "" ");
                e.printStackTrace(System.err);
            }
        }
    }

    最后为空时,采用全局默认的的handler。

    线程异常就说道这,已经说了不少了,下面简单说两句JVM的退出/停止。

    通常我们在课本上都会知道java.lang.System类有个方法是exit(int),这个会让程序退出,而实际上这个方法实际上是java.lang.Runtime类exit(int)的封装(其实System还有像gc()等很多方法,实际执行者都是这个java.lang.Runtime类对象),意义是告诉当前运行环境关闭运行,也就通常被理解为JVM“关闭”。

    说下Runtime类,这个类是个单例模式的类,即从头到尾程序只能获得同一个对象引用,即调用静态方法Runtime.getRuntime()所得。Runtime类负责和运行时环境相关的各种任务,有很多有趣的方法,比如很多应用(如Tomcat)中某些线程池中线程数目的确定和当前运行环境处理器个数有关,则可通过Runtime的availableProcessors()方法获得。这里重点说下exit()方法。

    还有一个概念需要说下,就是ShutdownHook,这个听起来“高端洋气上档次”的东西实际上就是一个Thread,但它的特殊就在于是一个hook,在JVM关闭(exit)的时候才会执行到。就如线程和中断的关系一样,JVM和exit()也是一样,exit()的执行并不是虚拟机马上关掉,而是安排结束时所要执行任务,按文档说明分上下两个过程,前面的过程就是shutdown对应Thread执行的过程。要想让虚拟机优雅的结束,exit()安排执行shutdownHook清理环境是必要的,还说Tomcat吧,终止的时候也会执行注册过的hook。执行hook的时候,通常守护/非守护线程也都仍然可以执行,但对hookd的新增和移除则不可以了。

    Runtime和shutdonwHook关联起来的方法,主要就是这俩了:

    • public void addShutdownHook(Thread hook)
    • public boolean removeShutdownHook(Thread hook)

    具体用法不用多说估计看文档都能懂,需要注意的地方就是,想要remove必需在add的时候留有hook的Thread对象引用。

    和Thread的stop()类似,Runtime也有halt()方法,但这是一个“暴力停机”的方法,不会考虑hook的执行,如果情况允许,exit()还是优雅的选择。

    转自:

    线程、Thread类和线程终止相关整理(上)

     线程、Thread类和线程终止相关整理(下)——线程异常&JVM停止

  • 相关阅读:
    【转载】Linux下各文件夹的含义和用途
    【转载】Linux 通过mount -o loop 配置本地.iso镜像为yum源(yum仓库)
    Fedora 和 RedHat 以及 SUSE 中 YUM 工具的使用
    【转】下载对应内核版本的asmlib
    【转】VMWare vCenter 6.0安装配置
    【转】在VMware中为Linux系统安装VM-Tools的详解教程
    【转】虚拟化(五):vsphere高可用群集与容错
    html拼接时onclick事件传递json对象
    bootstrap table 解析写死的json.并且把进度条放进列中。
    开发规范实体和值对象
  • 原文地址:https://www.cnblogs.com/549294286/p/3825029.html
Copyright © 2011-2022 走看看