Thread 源码解析
类注释
- 每个线程都有优先级,高优先级的线程可能会优先执行;
- 父线程创建子线程后,优先级、是否是守护线程等属性父子线程是一致的;
- JVM 启动时,通常都启动 main 非守护线程,以下任意一个情况发生时,线程就会停止:
- 退出方法被调用,并且安全机制允许这么做(比如调用 Thread.interrupt 方法)
- 所有非守护线程都消亡,或者从允许的方法正常返回,或者允许的方法抛出了异常;
- 每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字
线程的基本概念
线程的状态
初始状态(NEW)
NEW 表示线程创建成功,但没有运行,在 new Thread 之后,没有 start 之前,线程的状态都是 NEW;
就绪状态
Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为 RUNNABLE
READY
- 就绪状态只是表示此线程能被运行,调度程序没有挑选到,就一直是就绪状态
- 调用线程的 start 方法,线程进入就绪状态
- 当前线程 sleep 方法结束、其他线程 join 结束、等待用户输入完毕、某个线程拿到对象锁,这些线程也将进入就绪状态
- 当前线程时间片用完了,调用当前线程的 yield 方法,当前线程进入就绪状态
- 锁池的线程拿到对象锁后,进入就绪状态
RUNNING
线程调度程序从可运行池中选择一个线程作为当前线程时,线程所处的状态。这也是线程进入运行状态的唯一一种方式。
阻塞状态(BLOCKED)
线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED,BLOCKED 表示阻塞的意思;
等待(WAITING)
处于这种状态的线程不会被分配 CPU 执行时间,它们要等待被显示地唤醒,否则会处于无限等待状态
超时等待(TIME_WAITING)
处于这种状态的线程不会被分配 CPU 执行时间,也无需无限期等待被其他线程显示地唤醒,在到达一定时间后他们会自动唤醒
终止状态(TERMINATED)
当线程运行完成时,或者主线程的 main 方法完成时,被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程调用 start 方法,会抛出 java.lang.IllegalThreadStateException 异常
优先级
优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行。在 Java 源码中,优先级从低到高分别是 1 - 10 。线程默认 new 出来的优先级都是 5 ,源码如下:
java.lang.Thread.java
/**
* 最低优先级
* 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;
守护线程
我们创建的线程都是非守护线程。创建守护线程时,需要将 Thread 的 deamon 属性设置成 true,守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程, JVM 仍然会退出。
我们在工作中,可能会写一些工具做一些监控的工作,这时我们都是用守护子线程去做,这样即使监控抛出异常,但因为是子线程,所以也不会影响到业务主线程,因为是守护线程,所以 JVM 也无需关注监控是否正在运行,该退出就退出,所以对业务不会产生任何影响。
线程的两种初始化方式
无返回值的线程主要有两种初始化方式:
-
继承 Thread,成为 Thread 的子类
public class MyThread extends Thread{ @Override public void run() { System.out.println("do something"); } // 调用start方法即可 public static void main(String[] args) { new MyThread().start(); } }
我们来看一下 start 方法的源码:
public synchronized void start() { // 如果线程没有初始化,抛异常 if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); // started标识符 boolean started = false; try { // 这里会创建一个新的线程,执行完成之后,新的线程已经在运行了 // 动作发生之前标识符是 false,发生完成之后变成 true start0(); started = true; } finally { try { // 如果失败,把线程从线程组中移除 if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } // 开启新线程使用的是native方法 private native void start0();
-
实现 Runnable 接口,作为 Thread 的参数
public static void main(String[] args) { Thread thread = new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); // 开启一个新的子线程执行 thread.start(); // 使用主线程执行 thread.run(); }
这种就是实现 Runnable 接口,并作为 Thread 构造器的入参,我们调用时使用了两种方式,可以根据情况使用 start 或 run 方法,使用 start 方法会开启子线程来执行 run 方法里面的内容,使用 run 方法则执行的还是主线程。
线程初始化
线程初始化的源码有点长,我们只看比较重要的代码(不重要的代码被我删掉了):
// 无参构造器,线程名字自动生成
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* Initializes a Thread.
*
* @param g代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
* @param target是我们要运行的对象
* @param name如果不传默认是 "Thread-" + nextThreadNum()
* @param stackSize 可以设置堆栈的大小
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 当前线程作为父线程
Thread parent = currentThread();
this.group = g;
// 子线程会继承父线程的守护属性
this.daemon = parent.isDaemon();
// 子线程继承父线程的优先级属性
this.priority = parent.getPriority();
// classLoader
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
// 当父线程的 inheritableThreadLocals 的属性值不为空时
// 会把 inheritableThreadLocals 里面的值全部传递给子线程
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
/* Set thread ID */
// 线程 id 自增
tid = nextThreadID();
}
从初始化源码中可以看到,很多属性,子线程都是直接继承父线程的,包括优先级、守护线程等
线程的其他操作
join
join 的意思就是当前线程等待另一个线程执行完成之后,才能继续操作。
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
System.out.println(main.getName() + "线程 is run");
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程 is run");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程 is end");
});
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + "线程 is end");
}
}
输出:
main线程 is run
Thread-0线程 is run
Thread-0线程 is end
main线程 is end
执行的结果,就是主线程在执行 thread.join (); 代码后会停住,会等待子线程沉睡 30 秒后再执行,这里的 join 的作用就是让主线程等待子线程执行完成
从图中可以看出,主线程一直等待子线程沉睡 30s 后才继续执行,在等待期间,主线程的状态也是 TIMED_WAITING。
yield
yield 是个 native 方法
public static native void yield();
意思是当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程过度使用 cpu,我们在写 while 死循环的时候,预计短时间内 while 死循环可以结束的话,可以在循环里使用 yield 方法,防止 cpu 一直被 while 死循环霸占。
有点需要说明的是,让步不是不执行,重新竞争时,cpu 也有可能重新选中这个线程。
sleep
sleep 也是 native 方法,参数是时间数,作用是使当前线程沉睡多久,沉睡时不会释放锁资源。
interrupt
interrupt 中文是打断的意思,意思是可以打断中止正在处于等待的线程,比如:
- Object#wait()、Thread#join()、Thread#sleep() 这些方法运行后,线程的状态是 WAITING 或TIME_WAITING,这时候打断这些线程,就会抛出 InterruptedException 异常,使线程的状态变成 TERMINATED;
- 如果 I/O 操作被阻塞了,我们主动打断当前线程,连接会被关闭,并抛出 ClosedByInterruptException 异常;
我们举一个例子来说明如何打断 WAITING 的线程,代码如下:
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
System.out.println(main.getName() + "线程 is run");
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程 is run");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程 is end");
});
thread.start();
// 主线程等待1s后,打断子线程
Thread.sleep(1000L);
thread.interrupt();
}
main线程 is run
Thread-0线程 is run
java.lang.InterruptedException: sleep interrupted
Thread-0线程 is end
at java.lang.Thread.sleep(Native Method)
at com.study.interview.ThreadJoin.lambda$main$0(ThreadJoin.java:11)
at java.lang.Thread.run(Thread.java:748)
但是不能中断正在运行的线程
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
System.out.println(main.getName() + "线程 is run");
Thread thread = new Thread(() -> {
int i = 0;
while (true){
System.out.println(i++);
}
});
thread.start();
// 主线程等待1s后,打断子线程
Thread.sleep(1000L);
thread.interrupt();
}
这样是打断不了的