线程的属性总结
1. 线程有哪些常见属性?
● 线程ID:线程用ID来标识出不同线程
● 线程名字(Name):让用户或者程序猿开发调试或运行中定位线程的问题等。
● 守护线程(isDaemon):当为true时,代表该线程为守护线程,false为非守护线程,也可以称作用户线程。
● 线程优先级(Priority):作用是告诉线程调度器,希望那个线程多运行,那个线程少运行。
1.1 线程ID
● 线程ID从1开始,JVM运行起来后,我们自己创建的线程Id 早已不是0
public class ID {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
System.out.println(thread.getName() + ": " + thread.getId());
}
}
main: 1
Thread-0: 11
Process finished with exit code 0
● 在线程初始化中,有tid赋值
/* For generating thread ID */
private static long threadSeqNumber;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
/* Set thread ID */
tid = nextThreadID();
}
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
● threadSeqNumber没有赋值,所以为0,而nextThreadID()的++threadSeqNumber使得线程从ID从1开始.
● 那么为什么答应出来的子线程ID是11呢?
○ 如果你是用的IDEA,可以通过debug下个断点在最后一句上面,然后看一下当前线程的情况。
○ 实际就是JVM帮我们起了一些线程。
1.2 线程名字
- 默认线程名字源码分析:
○ 在没有指定线程名字的时候,线程默认传一个"Thread-" + nextThreadNum()作为名字
○ 而nextThreadNum获取到的threadInitNumber则是一个从0自增的一个静态变量。因为用了synchronized,所以是不会重复的。
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
- 如何修改线程名字
○ 一个是通过构造方法
○ 另一个是通过setName(String name)设置
public Thread(String name) {
init(null, null, name, 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
...
}
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);
}
}
○ 注意setName(String name)调用的时候,当线程是NEW状态的时候,设置name会一同把JVM里面的CPP的线程名字设置好,而如果不是NEW状态则只能修改java中我们可以看到的名称。
1.3 守护线程(Daemon)
● 作用:给用户线程提供服务
● 三个特性:
○ 线程默认类型继承自父线程
○ 被谁启动
○ 不影响JVM退出
● 守护线程和普通线程的区别
○ 整体没有太大区别
○ 唯一的区别是是否影响JVM的退出
● 常见面试问题
○ 守护线程和用户线程的区别
○ 我们是否需要给线程设置为守护线程?
■ 不需要,如果设置生守护线程,在JVM退出时会忽略你正在执行的任务,如果你正在执行一些数据操作,那么就会造成数据不一致了。
1.4 线程优先级
● 在Java中优先级有个10个等级,默认为5,通过setPriority(int newPriority)设置
● 但是我们程序在编码的时候,不应该依赖于优先级
○ 高度依赖于操作系统,不同的操作系统在实现优先级执行的时候不一样(windows中只有7个等级,更甚有的没有优先级。)设置优先级是不可靠的
○ 优先级可能会被操作系统改变
/**
* 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) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
1.5 常见问题
- 什么时候我们需要设置守护线程?
- 我们应该如何应用线程优先级来帮助程序运行?有哪些禁忌?
- 不同的操作系统如何处理优先级问题?
2. 未捕获异常处理UncaughtException
2.1 使用UncaughtExceptionHandler处理
- 为什么要使用UncaughtExceptionHandler来处理?
○ 主线程可以轻松发现异常,而子线程却不行
/**
* 单线程抛出处理有异常堆栈
* 而多线程,子线程发生异常有什么不同?
*
*/
public class ExceptionInChild {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
throw new RuntimeException();
});
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " +i);
}
}
}
运行结果
Exception in thread "Thread-0" java.lang.RuntimeException
at com.imyiren.concurrency.thread.uncaughtexception.ExceptionInChild.lambda$main$0(ExceptionInChild.java:14)
at java.lang.Thread.run(Thread.java:748)
main: 0
main: 1
main: 2
main: 3
main: 4
Process finished with exit code 0
○ 由上可看出,子线程报错,丝毫不印象主线程的执行。
○ 子线程的异常无法用传统的方法捕获
/**
* 1. 不加try-catch 抛出四个异常
* 2. 加了try-catch 期望捕获第一个线程的异常,线程234应该不运行,希望看到CaughtException
* 3. 执行时发现,根本没有CaughtException,线程234依旧运行并抛出异常
*
*/
public class CantCatchDirectly {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
throw new RuntimeException();};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
Thread thread4 = new Thread(runnable);
try {
thread1.start();
Thread.sleep(200);
thread2.start();
Thread.sleep(200);
thread3.start();
Thread.sleep(200);
thread4.start();
} catch (RuntimeException e) {
System.out.println("caught exception");
}
}
}
运行说明
Exception in thread "Thread-0" java.lang.RuntimeException
at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-1" java.lang.RuntimeException
at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-2" java.lang.RuntimeException
at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-3" java.lang.RuntimeException
at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
○ 如上,无法用传统的方法来捕获异常信息。
○ try-catch是针对主线程的,而不是针对子线程的。throw new RuntimeException()是运行在子线程的。
- 解决上面的主线程无法捕获的问题:
● 方案一(不推荐):在run()方法中进行try-catch
/**
* 方案一:在run方法中try-catch
*/
public class CatchExceptionInRun {
public static void main(String[] args) {
new Thread(() -> {
try {
throw new RuntimeException();
} catch (RuntimeException e) {
System.out.println("Caught Exception ...");
}
}).start();
}
}
● 方案二:利用UncaughtExceptionHandler接口处理
○ 先看下线程异常处理器的调用策略 在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);
}
}
}
a. 给程序统一设置
■ 首先自定义一个Handler
/**
* 自定义异常Handler
*/
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private String name;
public MyUncaughtExceptionHandler(String name) {
this.name = name;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.WARNING, name + "caught thread exception : " + t.getName());
}
}
■ 然后设置默认处理器
/**
* 使用自定义的handler
*/
public class CatchByOwnUncaughtExceptionHandler {
public static void main(String[] args) throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("catch-handler"));
Runnable runnable = () -> {
throw new RuntimeException();
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
Thread.sleep(200);
thread2.start();
}
}
运行说明
月 12, 2020 2:09:20 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-0
二月 12, 2020 2:09:20 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-1
二月 12, 2020 2:09:21 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-2
二月 12, 2020 2:09:21 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: catch-handlercaught thread exception : Thread-3
Process finished with exit code 0
■ 如上可以看到 线程异常处理是使用我们自定义的处理器。
a. 可以给每个线程单独设置
■ 可以通过thread.setUncaughtExceptionHandler(handler)设置
b. 给线程池设置
■ 可以通过ThreadPoolExecutor来处理