Java并发基础学习(五)——线程属性以及异常处理

前言

总结到这里,线程的基础知识几乎完成大半了,这篇博客对线程的属性以及异常处理做一个小结,之后会总结线程的安全性,由线程的安全问题引出Java线程的内存模型的学习。

线程的各个属性

总览

一般Java中线程的属性有如下几个

属性名称 用途
编号(线程ID) 用于标示不同的线程,每个线程有自己的ID
名称(Name) 用于给线程命名,默认为Thread-i,为了让用户和程序开发者方便调试和定位问题,一般需要自己给线程起一个容易识别的名称
是否是守护线程(isDaemon) true代表是守护线程,false表示不是守护线程
优先级(Priority) 告知线程调度器,用户希望那些线程相对多运行,那些少运行

线程ID

线程id是不能修改的,分配之后就固定,从0开始,分配之后就不会被修改

/**
 * autor:liman
 * createtime:2021/9/19
 * comment:线程Id演示,id变量从0开始,但是是++threadId的形式返回,所以初创线程的主线程id为1
 */
public class ThreadIdProperty {
    
    

    public static void main(String[] args) {
    
    
        System.out.println("主线程的id:"+Thread.currentThread().getId());
        new Thread(()->{
    
    
            System.out.println("子线程的id:"+Thread.currentThread().getId());
        }).start();
    }

}

运行结果:

请添加图片描述

会发现初始化的第一个子线程的id并不是想象中的2,这是因为有很多JVM还为我们建立了很多守护线程

线程名字

线程名默认为Thread-,如果没有给线程显示指定线程名,则就会默认为Thread-,这样不利于调试与排查问题,因此在实际开发中一般都要指定线程名,其源码如下

/**
 * Changes the name of this thread to be equal to the argument
 * <code>name</code>.
 * <p>
 * First the <code>checkAccess</code> method of this thread is called
 * with no arguments. This may result in throwing a
 * <code>SecurityException</code>.
 *
 * @param      name   the new name for this thread.
 * @exception  SecurityException  if the current thread cannot modify this
 *               thread.
 * @see        #getName
 * @see        #checkAccess()
 */
public final synchronized void setName(String name) {
    
    
    checkAccess();
    if (name == null) {
    
    
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    if (threadStatus != 0) {
    
    
        setNativeName(name);
    }
}

源码中看的出来,线程其实还存在一个nativeName,其中nativeName在线程启动之后就已经固定,不能再重新设置,相反线程的普通名称name,这可以在启动之后修改。

守护线程

这个属性平时用的不多,但是还是需要了解。其实线程分为用户线程(比如main线程,其实是用户线程)和守护线程,守护线程的作用就是给用户线程提供服务。当JVM中不存在用户线程之后,JVM停止,守护线程也会随之停止。

线程类型默认继承至父线程。换句话说:通过守护线程创建出来的线程,依旧是守护线程,通过用户线程创建出来的线程依旧是用户线程,比如在main函数中创建的子线程,其实就是用户线程。

可以通过setDaemon方法改变线程的守护属性。

通常而言,守护线程是由JVM启动,但是守护线程并不影响JVM的退出,因为JVM的退出是判断当前是否还有未运行结束的用户线程,而与守护线程无关,但是JVM退出,则守护线程必然终止。

守护线程与普通线程,整体上并没有太大区别,只是区别在于二者时候会影响到JVM的退出上,一般而言,我们并不需要也不应该人为的将某个线程设置为守护线程。因为某些用户线程设置为守护线程之后,会在一定层度上影响JVM是否退出的判断,如果本身自己的用户线程在读取某个文件,突然被置成守护线程,JVM这个时候判断没有用户线程了,直接终止退出,会导致文件读取失败,这是不可取的。

优先级

Java中的线程存在10个优先级,线程的默认优先级为5。我们只需要知道这一点即可,了解其概念即可,在实际编码过程中,没有必要对其优先级作出修改,因为程序设计本身不应该依赖于线程的优先级,毕竟每个操作系统都有自己对应的优先级,因此这里不做过多的介绍。

如何处理线程的异常

线程的异常处理,并不想我们正常开发中的异常,只需要简单的try catch处理了就行,因为子线程的异常无法用传统方法捕获。

如果子线程异常

如果子线程抛出异常,如果没有正常处理,并不影响主线程的运行。

/**
 * autor:liman
 * createtime:2021/9/20
 * comment:
 */
public class ExceptionInChildThread implements Runnable {
    
    

    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new ExceptionInChildThread());
        thread.start();
        System.out.println("主线程运行");
        for(int i =0;i<500;i++){
    
    
            System.out.println(i);
        }
    }

    @Override
    public void run() {
    
    
        throw new RuntimeException("这个是子线程异常");
    }
}

运行结果:

请添加图片描述

子线程虽然抛出异常,但是也仅仅抛出了异常,程序依旧继续欢快的运行

传统的try-catch并不能处理

传统的try-catch并不能处理子线程抛出的异常。

/**
 * autor:liman
 * createtime:2021/9/20
 * comment:想在主线程中捕获子线程的异常,想象很美好,其实主线程无法处理子线程抛出的异常
 * 因为其中一个线程抛出了异常,其余的线程依旧运行完成,这说明线程的异常不能用传统的try catch来处理
 */
public class CatchSubThreadExceptionInMainThread implements Runnable {
    
    

    public static void main(String[] args) {
    
    
        try {
    
    
            Thread thread01 = new Thread(new CatchSubThreadExceptionInMainThread());
            thread01.start();
            Thread.sleep(300);//这里睡眠,是让方便线程处理异常,如果线程1抛出异常,后续线程应该不启动才对
            Thread thread02 = new Thread(new CatchSubThreadExceptionInMainThread());
            thread02.start();
            Thread thread03 = new Thread(new CatchSubThreadExceptionInMainThread());
            thread03.start();
            Thread thread04 = new Thread(new CatchSubThreadExceptionInMainThread());
            thread04.start();
        }catch (Exception e){
    
    
            System.out.println("主线程捕获到子线程的异常,并进行处理,异常信息为"+e);
        }
    }

    @Override
    public void run() {
    
    
        throw new RuntimeException("这个是子线程异常");
    }
}

运行结果,如果线程1抛出了异常,正常情况代码应该停止,而实际情况是线程2,线程3,线程4都继续运行

请添加图片描述

这是因为try-catch只能处理当前线程抛出的异常,而当前线程是在主线程中,自然不能子线程的异常。主线程中的sout相关的代码并没有输出就是最好的证明。

正确的处理方式

通过全局的异常处理类——UncaughtExceptionHandler来处理线程的异常。其对应的源码如下

@FunctionalInterface
public interface UncaughtExceptionHandler {
    
    
    /**
     * Method invoked when the given thread terminates due to the
     * given uncaught exception.
     * <p>Any exception thrown by this method will be ignored by the
     * Java Virtual Machine.
     * @param t the thread
     * @param e the exception
     */
    void uncaughtException(Thread t, Throwable e);
}

只有一个简单的方法。在ThreadGroup类中有对于其相关的实现

/**
 * Called by the Java Virtual Machine when a thread in this
 * thread group stops because of an uncaught exception, and the thread
 * does not have a specific {@link Thread.UncaughtExceptionHandler}
 * installed.
 * <p>
 * The <code>uncaughtException</code> method of
 * <code>ThreadGroup</code> does the following:
 * <ul>
 * <li>If this thread group has a parent thread group, the
 *     <code>uncaughtException</code> method of that parent is called
 *     with the same two arguments.
 * <li>Otherwise, this method checks to see if there is a
 *     {@linkplain Thread#getDefaultUncaughtExceptionHandler default
 *     uncaught exception handler} installed, and if so, its
 *     <code>uncaughtException</code> method is called with the same
 *     two arguments.
 * <li>Otherwise, this method determines if the <code>Throwable</code>
 *     argument is an instance of {@link ThreadDeath}. If so, nothing
 *     special is done. Otherwise, a message containing the
 *     thread's name, as returned from the thread's {@link
 *     Thread#getName getName} method, and a stack backtrace,
 *     using the <code>Throwable</code>'s {@link
 *     Throwable#printStackTrace printStackTrace} method, is
 *     printed to the {@linkplain System#err standard error stream}.
 * </ul>
 * <p>
 * Applications can override this method in subclasses of
 * <code>ThreadGroup</code> to provide alternative handling of
 * uncaught exceptions.
 *
 * @param   t   the thread that is about to exit.
 * @param   e   the uncaught exception.
 * @since   JDK1.0
 */
public void uncaughtException(Thread t, Throwable e) {
    
    
    if (parent != null) {
    
    
        //如果parent不为空,则读取parent中的线程异常处理器
        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);
        }
    }
}

从源码中我们可以看出,我们需要做的,就是给线程组设置我们自定义的异常处理器即可。

1、实现我们自己的异常处理器

/**
 * autor:liman
 * createtime:2021/9/20
 * comment:自定义的线程异常处理器
 */
@Slf4j
public class SelfThreadExceptionHandler implements Thread.UncaughtExceptionHandler{
    
    
    @Override
    public void uncaughtException(Thread t, Throwable e) {
    
    
       //这里就是我们自己的线程异常处理类
       log.error("线程:{},运行出现异常,异常信息为:{},请进行处理",t.getName(),e.getMessage());
        //TODO:返回前端统一描述,或者事件通知监控系统
    }
}

2、给所有线程设置默认的异常处理器

/**
 * autor:liman
 * createtime:2021/9/20
 * comment:正常的线程异常处理实例
 */
public class RightThreadExceptionDemo implements Runnable{
    
    


    public static void main(String[] args) {
    
    

        //设置线程的异常处理器
        Thread.setDefaultUncaughtExceptionHandler(new SelfThreadExceptionHandler());

        Thread thread01 = new Thread(new RightThreadExceptionDemo());
        thread01.start();
        Thread thread02 = new Thread(new RightThreadExceptionDemo());
        thread02.start();
        Thread thread03 = new Thread(new RightThreadExceptionDemo());
        thread03.start();
        Thread thread04 = new Thread(new RightThreadExceptionDemo());
        thread04.start();
    }

    @Override
    public void run() {
    
    
        throw new RuntimeException("子线程运行出现异常");
    }
}

一些面试问题

1、什么时候我们需要设置守护线程?

通常情况下不需要,也不建议强制将用户线程置为守护线程。

2、我们应该如何用线程优先级来帮助程序运行,有哪些需要注意的?

程序设计不应该依赖于操作系统的优先级,不同的操作系统优先级等级不一样,不应用线程优先级来帮助程序运行。

3、针对线程的异常,如何处理?

推荐利用全局的异常处理器来处理

4、run方法是否可以抛出异常?如果抛出异常,线程的状态会如何,该如何处理抛出的异常?

run方法在接口声明的时候,就不让抛出异常,如果抛出异常,线程会结束运行,针对抛出的异常,其实可以在run中用try-catch处理,但是不推荐,建议用UncaughtExceptionHandler来处理。

总结

简单梳理了一下线程的基本属性和线程的异常处理。

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/120797292