【转】线程、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停止

【转】线程、Thread类和线程终止

时间: 2024-11-09 21:56:30

【转】线程、Thread类和线程终止的相关文章

java 第54节 继承Thread类创建线程

2016-07-01 1 继承Thread类创建线程 class 类名 extends Thread{ public void runa(){ //相关代码 } } package com.java1995; /** * 继承Thread类创建线程 * @author Administrator * */ public class MyThread extends Thread{ //构造器:给线程命名 public MyThread(String name){ super(name); } /

继承Thread类创建线程

1 继承Thread类创建线程 class 类名 extends Thread{ public void run(){ //相关代码 } } 2 实现Runnable接口 1 实现Runnable接口 class类名 implements Runnable{ public void run(){ } } A a=new A(); Thread t=new Thread(A); Thread t1=new Thread(A,"线程1"); 2两种方法的比较 extends Thread

PYTHON——多线程:Thread类与线程函数

Thread类与线程函数 可以使用Thread对象的join方法等待线程执行完毕:主线程(main()函数)中调用Thread对象的join方法,并且Thread对象的线程函数没有执行完毕,主线程会处于阻塞状态.使用Thread类实现多线程的步骤:1.创建Thread类的实例:2.通过Thread类的构造方法的target关键字参数执行线程函数:通过args关键字参数指定传给线程函数的参数.3.调用Thread对象的start方法启动线程.下面例子功能:使用Thread对象启动2个线程,并在各自

PYTHON——多线程:Thread类与线程对象

Thread类与线程对象 Thread类构造方法的target关键字参数不仅可以是函数,还可以是一个对象,可以称这个对象为线程对象.其实,线程调用的仍然是函数,只是这个函数用对象进行了封装.这么做的好处是可以将线程函数相关的代码都放在这个对象对应的类中,更能体现面向对象的封装性. 线程对象对应的类需要有一个可以传入线程函数和参数的构造方法,而且在类中还必须有一个名为"__call__()"的方法.当线程启动时,会自动调用线程对象的"__call__()"方法,然后在

Java多线程01(Thread类、线程创建、线程池)

Java多线程(Thread类.线程创建.线程池) 第一章 多线程 1.1 多线程介绍 1.1.1 基本概念 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序. 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 1.1.2 单线程程序 - 从入口m

学习笔记2:java中Thread类与线程的创建

线程 是程序中的执行线程.Java 虚拟机允许应用程序并发地运行多个执行线程. 每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程.每个线程都可以或不可以标记为一个守护程序.当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序. 当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法).Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

线程Thread类的start()方法和run()方法

一.初识 java的线程是通过java.lang.Thread类来实现的.VM启动时会有一个由主方法所定义的线程.可以通过创建Thread的实例来创建新的线程.每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体.通过调用Thread类的start()方法来启动一个线程. 在Java当中,线程通常都有五种状态,创建.就绪.运行.阻塞和死亡. 第一是创建状态.在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态. 第二是就绪状态.

网络编程-线程-3、通过继承Thread类创建线程

知识点:之前第一节介绍创建线程的方法是:通过threading.Thread(target=函数名不要加括号)创建一个对象,通过对象调用start方法创建并启动线程:                                                                       这一节介绍另外一种创建线程的方法:写一个子类,继承Thread类,里面定义一个run方法即可通过该子类创建一个线程 代码如下,解释看注解: #!/usr/bin/env python # co

Java 线程--继承java.lang.Thread类实现线程

现实生活中的很多事情是同时进行的,Java中为了模拟这种状态,引入了线程机制.先来看线程的基本概念. 线程是指进程中的一个执行场景,也就是执行流程,进程和线程的区别: 1.每个进程是一个应用程序,都有独立的内存空间. 2.同一个进程中的线程共享其进程中的内存和资源. (共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的栈内存) 我们还需要了解以下基本内容: 1.什么是进程? 一个进程对应一个应用程序.例如:在Windows操作系统启动word就表示启动了一个进程.在Java开发环境下