Thread类源码剖析

Thread类源码剖析

一、Thread类的简介

Java虚拟机允许应用程序同时运行多个执行线程,每个线程都有一个优先级,具有较高优先级的线程优先于具有较低优先级的线程执行。每个线程可能也可能不被标记为守护进程,这默认取决于创建该线程的父线程,优先级大小的设置也是同理。
当Java虚拟机启动时,通常有一个非守护线程(它通常调用某个指定类的main方法,即使就是main线程)。Java虚拟机会一直执行直到发生以下任一情况:

  • Runtime类的exit方法已被调用,并且SecurityManager安全管理器已允许进行退出操作。
  • 所有非守护线程都已死亡,原因是从对run方法的调用返回或抛出传播到run方法之外的异常。

二、创建线程的四种方式

  • 继承Thread类并重写run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}

@Override
public void run() {
// compute primes larger than minPrime
}
}

Thread thread = new PrimeThread(143);
thread.start();
  • 实现Runnable接口及其run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
}
}

PrimeRun p = new PrimeRun(143);
new Thread(p).start();
  • 实现Callable接口及其call方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PrimeCall implements Callable<Long> {
long minPrime;
PrimeCall(long minPrime) {
this.minPrime = minPrime;
}

@Override
public Long call() throws Exception {
// compute primes larger than minPrime
return 149L;
}
}

FutureTask<Long> futureTask = new FutureTask<>(new PrimeCall(143));
Thread thread = new Thread(futureTask);
thread.start();
  • 线程池动态调度或创建线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}

@Override
public void run() {
// compute primes larger than minPrime
}
}

PrimeRun p = new PrimeRun(143);
ExecutorService threadPool = Executors.newFixedThreadPool(100);
threadPool.execute(p);

三、Thread类的重要属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 线程的名称
private volatile String name;
// 线程的优先级
private int priority;
// 守护线程的标志位
private boolean daemon = false;
// 保留给JVM独占使用的字段,我们可以不用理会
private boolean stillborn = false;
private long eetop;
// 线程的运行任务
private Runnable target;
// 线程所归属的线程组
private ThreadGroup group;
// 线程的上下文类加载器
private ClassLoader contextClassLoader;
/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;
// 未指定名称的线程会自动编号
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
// ThreadLocal类的底层实现就是依赖每个Thread对象中的threadLocals对象
ThreadLocal.ThreadLocalMap threadLocals = null;
// InheritableThreadLocal类的底层实现就是依赖每个Thread对象中的inheritableThreadLocals对象
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
// 请求的栈大小,我们暂时忽略
private final long stackSize;
// 线程ID
private final long tid;
// 生成线程ID
private static long threadSeqNumber;
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
// Java的线程状态,默认表示尚未启动
private volatile int threadStatus;
// 中断方法需要使用到,后面会看到
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
// 线程的最小优先级
public static final int MIN_PRIORITY = 1;
// 线程的默认优先级
public static final int NORM_PRIORITY = 5;
// 线程的最大优先级
public static final int MAX_PRIORITY = 10;

我们上面谈到了Thread类的一些重要属性,基本涵括了主要的属性,这些属性也是我们后面构造方法和其他重要方法操作的对象,遇到时我们可能还会详细展开说一下。

四、Thread类的构造方法

image-20231106150406467

注意Thread有很多重载的构造方法,不过其实都是调用图中标注的参数最全的构造方法(前面有个红色的锁标记,表示该构造方法是私有的,不可调用,只作为其他构造方法内部使用),下面我们单独看看这个构造方法里面做了那些事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 线程的名称不能为空,如果没有显示指定则为默认名称Thread-n
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 设置新线程的父线程为当前创建线程
Thread parent = currentThread();
// 获取应用程序的安全管理器,默认为空
SecurityManager security = System.getSecurityManager();
// 没有显示指定线程组的情况:
if (g == null) {
/* Determine if it's an applet or not */
// 如果存在安全管理器,优先使用安全管理器的线程组
if (security != null) {
g = security.getThreadGroup();
}
// 如果不存在安全管理器或者安全管理器的线程组为空,则使用父线程的线程组!
if (g == null) {
g = parent.getThreadGroup();
}
}

// 检查确定当前正在运行的线程(父线程)是否有权修改此线程组
g.checkAccess();
// 检查安全管理器的安全策略和权限
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(
SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
// 递增线程组中未启动的线程计数
g.addUnstarted();
// 设置线程组
this.group = g;
// 守护线程与否和优先级大小默认与父线程保存一致
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
// 设置上下文类加载器
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;
// 调用JNI本地方法设置线程的优先级,注意最大不能超过所属线程组的优先级
setPriority(priority);
// 如果inheritThreadLocals为true且父线程的inheritableThreadLocals字段不为空,则会复制父线程的本地变量
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 暂存指定的栈大小,不过有些虚拟机会直接忽略这个参数
this.stackSize = stackSize;
// 设置线程ID
this.tid = nextThreadID();
}

可以看到,Thread类的构造也没有我们想象的那么复杂,唯一比较难理解的是安全管理器的方面,不过默认我们的应用程序并不会开启安全管理器,除非我们通过==System.setSecurityManager(new SecurityManager());==,或者通过启动参数==-Djava.security.manager==,注意安全管理器配置文件的内容类似于下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//
// This system policy file grants a set of default permissions to all domains
// and can be configured to grant additional permissions to modules and other
// code sources. The code source URL scheme for modules linked into a
// run-time image is "jrt".
//
// For example, to grant permission to read the "foo" property to the module
// "com.greetings", the grant entry is:
//
// grant codeBase "jrt:/com.greetings" {
// permission java.util.PropertyPermission "foo", "read";
// };
//

// default permissions granted to all domains
grant {
// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";

// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission
"java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission
"java.vm.specification.version", "read";
permission java.util.PropertyPermission
"java.vm.specification.vendor", "read";
permission java.util.PropertyPermission
"java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};

我们可以通过==-Djava.security.manager -Djava.security.policy=”/usr/local/java.policy”==同时指定配置文件的路径。

五、Thread类的六种状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public enum State {
/**
* 尚未启动的线程的线程状态。
*/
NEW,

/**
* 可运行线程的线程状态。
* 处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统的其他资源,例如处理器。
*/
RUNNABLE,

/**
* 线程被阻塞等待监视器锁的线程状态。
* 处于阻塞状态的线程正在等待监视器锁进入同步块/方法,或者在调用Object.wait后重新进入同步块/方法。
*/
BLOCKED,

/**
* 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
* - Object.wait with no timeout
* - Thread.join with no timeout
* - LockSupport.park
* 处于等待状态的线程正在等待另一个线程执行特定操作。
* 例如,对某个对象调用了Object.wait()的线程正在等待另一个线程对该对象调用Object.notify()或Object.notifyAll()。
* 调用Thread.join()的线程正在等待指定线程终止。
*/
WAITING,

/**
* 具有指定等待时间的等待线程的线程状态。由于以指定的正等待时间调用以下方法之一,线程处于定时等待状态:
* - Thread.sleep
* - Object.wait with timeout
* - Thread.join with timeout
* - LockSupport.parkNanos
* - LockSupport.parkUntil
*/
TIMED_WAITING,

/**
* 已终止线程的线程状态。线程已完成执行。
*/
TERMINATED;
}

Java 线程状态变迁图

六、Thread类的重要方法

第一个出场的就是编写多线程程序基本必备的start方法,JVM虚拟机会启动新线程并调用线程的run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public synchronized void start() {
// 零值表示线程状态NEW
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 通知线程组该线程将要启动,可以加入组中的线程列表并且线程组的未启动线程数递减
group.add(this);
// 线程启动成功的标记
boolean started = false;
try {
// 调用JNI本地方法启动线程
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 */
}
}
}

private native void start0();

注意Thread类也继承了Runnable方法并重写run方法,Thread的run方法的逻辑是:如果创建线程时传入了Runnable对象则调用Runnable对象的run方法,否则就是继承Thread类并重写该run方法。

1
2
3
4
5
6
@Override
public void run() {
if (target != null) {
target.run();
}
}

我们来看看第二个出场的方法,没错,它就是我们经常使用的Thread.sleep方法,使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。线程不会失去任何监视器/锁的所有权!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void sleep(long millis, int nanos)
throws InterruptedException {
// 检查参数有效性
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
// 纳秒单位会向上四舍五入
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
// 调用JNI本地方法的休眠操作
sleep(millis);
}

public static native void sleep(long millis) throws InterruptedException;

第三个出场的方法就是在线程同步尤其是主线程等待多个子线程返回的场景下经常使用的join方法,会阻塞等待线程执行结束返回,底层其实就是调用线程的wait方法等待。

注意,JVM会保证线程结束时调用线程的notifyAll方法确保等待的join方法成功返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public final synchronized void join(long millis)
throws InterruptedException {
// 记录开始时间
long base = System.currentTimeMillis();
// 已经等待的时长
long now = 0;
// 检查参数有效性
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// 永久等待线程结束的情况
if (millis == 0) {
while (isAlive()) {
// 如果线程还没有执行结束,则挂起等待
wait(0);
}
} else {
// 有限时间等待线程结束的情况
while (isAlive()) {
// 还需等待的剩余时长
long delay = millis - now;
// 已经到达等待时长界限
if (delay <= 0) {
break;
}
// 挂起等待若干时长
wait(delay);
// 更新已经等待的时长
now = System.currentTimeMillis() - base;
}
}
}

第四个出场的是我们的中断系列方法,对于wait系列、join系列、sleep系列的方法处于阻塞等待状态时线程被中断会抛出InterruptedException异常,并且清除线程中断状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void interrupt() {
// 线程自己调用自己的中断方法不需要任何检查
if (this != Thread.currentThread()) {
checkAccess();
// 线程可能阻塞在IO操作上
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
// 设置中断状态
interrupt0();
b.interrupt(this);
return;
}
}
}

// 设置中断状态
interrupt0();
}

与上面的interrupt方法搭配使用的常常还有下面的检查线程当前中断状态的一些方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 返回【当前线程】的中断状态并清空中断状态
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}

// 返回【调用线程】的中断状态,不清空中断状态
public boolean isInterrupted() {
return isInterrupted(false);
}

@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean ClearInterrupted);

七、Thread类的异常处理机制

我们有必要插一嘴,在JSR-166标准引入了UncaughtExceptionHandler函数式接口,它是当线程由于未捕获的异常而突然终止时调用的处理程序的接口。

当线程由于未捕获的异常而即将终止时,Java虚拟机将使用getUncaughtExceptionHandler查询线程的UncaughtExceptionHandler,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。如果线程尚未显式设置其UncaughtExceptionHandler,则其ThreadGroup对象将充当其UncaughtExceptionHandler。如果ThreadGroup对象对处理异常没有特殊要求,它可以将调用转发到默认的未捕获异常处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;


public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}

@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* 当给定线程由于给定的未捕获异常而终止时调用的方法。此方法抛出的任何异常都将被Java虚拟机忽略。
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}

如果没有指定线程的未捕获异常处理程序,则默认使用其线程组对象充当异常处理程序,因为ThreadGroup类也继承了UncaughtExceptionHandler接口(下面代码展示):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}
}

正常我们不做任何编码限制的情况下,创建的线程默认归属父线程即main线程的线程组,如果线程执行中抛出了未捕获的异常,JVM会调用线程组的uncaughtException方法,根据上面ThreadGroup的uncaughtException方法实现我们可以看出,会找到最终的根线程组并调用其defaultUncaughtExceptionHandler的uncaughtException方法,打印异常栈的追踪轨迹信息。

下面我们简单的实践一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ThreadTest {
public static void main(String[] args) {
System.out.println("Thread.currentThread().getThreadGroup() = " + Thread.currentThread().getThreadGroup());
System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
System.out.println("Thread.currentThread().getPriority() = " + Thread.currentThread().getPriority());
System.out.println("Thread.currentThread().getState() = " + Thread.currentThread().getState());
System.out.println("Thread.currentThread().getContextClassLoader() = " + Thread.currentThread().getContextClassLoader());
System.out.println("Thread.currentThread().getId() = " + Thread.currentThread().getId());
System.out.println("Thread.currentThread().getUncaughtExceptionHandler() = " + Thread.currentThread().getUncaughtExceptionHandler());
int i = 1 / 0;
System.out.println("---");
}
}

image-20231106175145641

从上图也能看出main线程的线程组信息,并且其未捕获异常处理程序默认也就是本身的线程组对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("I am ThreadA");
int i = 1 / 0;
System.out.println("I am King");
}, "ThreadA");
System.out.println("thread.getThreadGroup() = " + thread.getThreadGroup());
System.out.println("thread.getName() = " + thread.getName());
System.out.println("thread.getContextClassLoader() = " + thread.getContextClassLoader());
System.out.println("thread.getUncaughtExceptionHandler() = " + thread.getUncaughtExceptionHandler());
System.out.println("thread.getState() = " + thread.getState());
thread.start();
}
}

image-20231106175651070

从上图可以看出创建的新线程的线程组就是其父线程(main线程)的线程组,所用的未捕获异常处理程序默认也就是本身的线程组对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("I am ThreadA");
int i = 1 / 0;
System.out.println("I am King");
}, "ThreadA");
thread.setUncaughtExceptionHandler((t,e) -> {
System.out.println(t.getName() + " : " + e.getMessage());
});
System.out.println("thread.getThreadGroup() = " + thread.getThreadGroup());
System.out.println("thread.getName() = " + thread.getName());
System.out.println("thread.getContextClassLoader() = " + thread.getContextClassLoader());
System.out.println("thread.getUncaughtExceptionHandler() = " + thread.getUncaughtExceptionHandler());
System.out.println("thread.getState() = " + thread.getState());
thread.start();
}
}

image-20231106175902860

从上图能看出,当我们给创建的线程设置未捕获异常处理程序后,一旦线程因为未捕获异常而终止时,JVM会使用我们设置的未捕获异常处理程序处理该异常!