并发和多线程(五)--线程相关属性和线程异常处理
1、线程id和name
线程id是线程的唯一标识,不可修改,而线程名称是可以修改的。
public static void main(String[] args) { Thread thread = new Thread(); System.out.println("主线程ID为:"+Thread.currentThread().getId()); System.out.println("主线程ID为:"+thread.getId()); System.out.println("主线程name为:"+thread.getName()); thread.setName("thread58"); System.out.println("主线程name为:"+thread.getName()); }
结果: 主线程ID为:1 主线程ID为:12 主线程name为:Thread-0 主线程name为:thread58
从结果看到,主线程的id为1,所以线程的id也是从1开始的,而新建的子线程的id为12,而不是我们猜想的2。
通过查看源码,知道线程id的规则如下:
public long getId() { return tid; } //通过调用Thread构造器初始化Thread,而tid = nextThreadID() public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } private static synchronized long nextThreadID() { return ++threadSeqNumber; }
这里是++threadSeqNumber实现自增,那为什么子线程的id不是2呢,是因为Jvm在运行代码的时候还会启动别的线程帮助程序运行和处理,例如垃圾收集器等,通过debug我们就可以看到。
而线程的name值,只是对线程的命名,除了默认情况下的命名,我们还可以通过setName()修改,而且可以多个线程name相同。
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } private static synchronized int nextThreadNum() { return threadInitNumber++; }
通过源码看到,默认没有name的情况下,通过"Thread-" + nextThreadNum()进行命名,而这个方法是threadInitNumber++,所以从Thread-0开始。
2、守护线程
对于jvm来说,一般我们创建的线程都是用户线程,除了用户线程就是守护线程,守护线程为了程序运行而服务,守护用户线程。一般来说,守护线程被jvm启动,而用户线程被主线程启动。根本的区别就是,如果jvm发现没有当前没有用户线程在运行,jvm就会退出,守护线程对jvm退出没有影响。
线程类型默认继承父线程,例如,在main方法中创建一个线程,主线程就是用户线程,所以被创建的线程默认也是用户线程。同样的,守护线程创建的线程也是守护线程。
//判断当前线程是否Wie守护线程 public final boolean isDaemon() { return daemon; } //设置当前线程为守护线程。true,守护线程,false,用户线程 public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; }
PS:
开发过程中,不要将线程设置为守护线程,一旦设置为守护线程,jvm发现没有用户线程在运行,就直接关闭了,就会导致我们的程序没有执行完就关闭了。
3、线程优先级
线程优先级是指线程启动执行的优先级,对于Java来说,一共10个优先级,默认为5。准确的说,线程优先级也是继承父类优先级,如果你把主函数设置为8,新建的子线程默认也是8.
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10; //设置线程优先级 public final void setPriority(int newPriority) { } //获取线程优先级 public final int getPriority() { return priority; }
开发中,我们不应该依赖线程优先级,因为我们在使用多线程的时候,发现优先级高的线程不一定比优先级低的线程先执行,只能说概率更高而已。而且,1-10的优先级只是jvm的划分,总归要和OS挂钩的,不同的OS对优先级的划分不同,需要进行映射。例如Windows有7个优先级,1和2对应1,3和4对应2。。。而Linux系统下Java线程优先级会被忽略,不能起作用。在solaris系统中,又是不同的。
设置线程优先级,还有可能带来的问题,就是某些优先级低的线程可能一直无法获得CPU使用权,也就是一直保持"饥饿"状态。所以,综上,完全不建议修改线程优先级。
4、如何捕获线程异常?
public static void main(String[] args) { Thread thread = new Thread(new ThreadClass()); thread.start(); for (int i = 0; i < 100; i++) { System.out.println(i); } } @Override public void run() { throw new RuntimeException(); }
通过上面的代码的运行,可以看到,子线程发生异常,主线程还是继续运行,如果运行在服务器上面,可能都不知道程序有出现过异常。所以,我们需要对Thread的代码进行异常处理,例如try catch。
try catch处理线程异常:
try { Thread thread = new Thread(() -> { throw new RuntimeException(); }); Thread thread1 = new Thread(() -> { throw new RuntimeException(); }); Thread thread2 = new Thread(() -> { throw new RuntimeException(); }); Thread thread3 = new Thread(() -> { throw new RuntimeException(); }); thread.start(); thread1.start(); thread2.start(); thread3.start(); } catch (RuntimeException e) { System.out.println("当前线程发生异常"); }
结果: Exception in thread "Thread-0" Exception in thread "Thread-3" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.RuntimeException at com.diamondshine.Thread.MyRunnableClass.lambda$main$0(MyRunnableClass.java:18) at java.lang.Thread.run(Thread.java:745) java.lang.RuntimeException at com.diamondshine.Thread.MyRunnableClass.lambda$main$2(MyRunnableClass.java:24) at java.lang.Thread.run(Thread.java:745) java.lang.RuntimeException at com.diamondshine.Thread.MyRunnableClass.lambda$main$3(MyRunnableClass.java:27) at java.lang.Thread.run(Thread.java:745) java.lang.RuntimeException at com.diamondshine.Thread.MyRunnableClass.lambda$main$1(MyRunnableClass.java:21) at java.lang.Thread.run(Thread.java:745)
四个线程都发生异常,我们通过try catch去处理,发现子线程发生异常,并没有被catch捕获。原因是try catch可以捕获主线程的异常,却不能捕获子线程的异常,导致即使有了try catch也是不行的。正确的方式:
UncaughtExceptionHandler
//自定义UncaughtExceptionHandler @Slf4j public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t, Throwable e) { log.error("当前线程:{}发生异常,异常信息为:{}", t.getName(), e.getMessage()); //后续报警等相关逻辑 } }
public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); Thread thread = new Thread(() -> { throw new RuntimeException(); }); Thread thread1 = new Thread(() -> { throw new RuntimeException(); }); Thread thread2 = new Thread(() -> { throw new RuntimeException(); }); Thread thread3 = new Thread(() -> { throw new RuntimeException(); }); thread.start(); thread1.start(); thread2.start(); thread3.start(); }
结果: 16:02:42.288 [Thread-3] ERROR com.diamondshine.Thread.MyUncaughtExceptionHandler - 当前线程:Thread-3发生异常,异常信息为: java.lang.RuntimeException: null at com.diamondshine.Thread.MyRunnableClass.lambda$main$3(MyRunnableClass.java:26) at java.lang.Thread.run(Thread.java:745) 16:02:42.288 [Thread-0] ERROR com.diamondshine.Thread.MyUncaughtExceptionHandler - 当前线程:Thread-0发生异常,异常信息为: java.lang.RuntimeException: null at com.diamondshine.Thread.MyRunnableClass.lambda$main$0(MyRunnableClass.java:17) at java.lang.Thread.run(Thread.java:745) 16:02:42.288 [Thread-1] ERROR com.diamondshine.Thread.MyUncaughtExceptionHandler - 当前线程:Thread-1发生异常,异常信息为: java.lang.RuntimeException: null at com.diamondshine.Thread.MyRunnableClass.lambda$main$1(MyRunnableClass.java:20) at java.lang.Thread.run(Thread.java:745) 16:02:42.288 [Thread-2] ERROR com.diamondshine.Thread.MyUncaughtExceptionHandler - 当前线程:Thread-2发生异常,异常信息为: java.lang.RuntimeException: null at com.diamondshine.Thread.MyRunnableClass.lambda$main$2(MyRunnableClass.java:23) at java.lang.Thread.run(Thread.java:745)
从结果看,我们自定义的异常处理器有效,成功的捕获到异常,这时候不仅仅打印日志,可以根据自己的使用场景进行逻辑处理,例如:让监控系统报警等。