bash 本文的所有内容基于jdk1.8源码,如有错误,还望指出!!!
思维导图
继承关系
可以看到Thread的继承关系很简单。
Thread
就是实现了Runnable
接口,然后Runable是一个@FunctionalInterface
注解的接口。
@FunctionalInterface
这个注解是jdk1.8才有的
通过JDK8源码javadoc,可以知道这个注解有以下特点:
- 该注解只能标记在"有且仅有一个抽象方法"的接口上。
- JDK8接口中的静态方法和默认方法,都不算是抽象方法。
- 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
- 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
Runnable
Runnable
是一个接口,只有一个无参方法run
,任何一个类实现这个接口,他的实例就可以一个线程执行。
所以可以通过实现Runable的方法来创建一个线程。
Thread
Thread是Runnable的一个实现类,thread是程序中的执行线程。JVM允许程序同时运行多个线程。每个线程都有一个优先级,高优先级的线程较低优先级的线程先运行。每个线程可能也可能不会被标记为守护线程。当代码运行在一个线程中时创建一个新的线程,新的线程将设置和当前线程相同的优先级,当且仅当当前线程时守护线程时,新的线程会被标记为守护线程。
当JVM启动时,通常有一个非守护线程(通常调用main方法的类)。JVM在遇到下列情况之前持续运行:
- Runtime的exit方法被调用,并且安全管理器允许退出操作发生。
- 所有非守护线程死亡,要么调用run方法返回,要么抛出一个可以传播到run方法之外的异常。
有两种方法创建一个新线程:
- 申明一个Thread的子类,子类复写run方法。
- 实现Runnable接口,实现run方法。
两种创建方法的区别:
//1.继承Thread
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
//1.1启动线程
PrimeThread p = new PrimeThread(143);
p.start();
//2.实现Runnable
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
//2.1启动线程
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
从上面可以看到,实现Runnable的类需要将实例交给Thread,然后调用Thread#start()才能启动一个线程。
每个线程都有一个用于识别的名称。多个线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。
Thread默认构造方法将传递一个null参数或者抛出空指针异常。
构造方法
所有的构造方法最后都会调用一个init方法,先看看这个init方法:
/**
* 初始化一个线程
*
* @param g 线程的组名
* @param target 线程将运行的程序
* @param name 线程的名字
* @param stackSize 新线程所需的堆栈大小,或为零表示将忽略此参数。
* @param acc 要继承的 AccessControlContext,如果为 null,则为 AccessController.getContext()
* @param inheritThreadLocals 如果为true ,则从构造线程继承可继承线程局部变量的初始值
*
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals)
/**
g:如果没有特别指定,会由SecurityManager指定,会和当前线程相同。
*/
其他常见方法
yield
是一个本地方法,他用于向调度程序提示当前线程愿意让出cpu
的使用,但是调度程序可以随意忽略这个提示。
sleep
让当前线程休眠
start
是当前线程开始执行,jvm开始调用线程的run方法。线程不能多次启动,且一旦完成就不可能重新执行。
interrupt
中断线程
join
阻塞主线程,等到当前线程执行完继续执行主线程后面的代码。
优先级
高优先级的线程较低优先级的线程先运行。
/**
* 最小的优先级为1
*/
public final static int MIN_PRIORITY = 1;
/**
* 默认优先级是5
*/
public final static int NORM_PRIORITY = 5;
/**
* 最高的优先级
*/
public final static int MAX_PRIORITY = 10;
守护线程和非守护线程
【以下内容来自https://blog.csdn.net/weixin_30578677/article/details/97976448】
所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:
- thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
- 在Daemon线程中产生的新线程也是Daemon的。
- 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。