线程的实现与分析

摘要:

  1. Java 的线程模型
  2. Java 的线程状态
  3. Java线程的 join 实现原理

TOP 带着问题来分析

  1. Java 的线程模型
  2. Java 的线程状态
  3. Java线程的 join 实现原理

1. 线程模型

线程是操作系统调度的最小单位,实现线程有三种方式,而 Java Thread 采用的是 内核线程实现

1.1 用户层实现(N:1)

优点:

  • 用户线程切换不需要内核介入,切换很快、代价相对低
  • 用户线程操作成本低

缺点:

  • 用户线程自己进行管理,比较麻烦
  • 阻塞处理复杂
  • 如何将线程映射到其他处理器

1.2 内核线程实现(1:1)

每一个用户线程对应一个内核线程,内核去完成线程的创建和调度。

优点:

  • 阻塞处理比较简单
  • 充分利用硬件

缺点:

  • 用户线程操作涉及到内核调用,代价较高

通过跟踪 Java Thread 中 start() 方法:

start() -> native start0() -> JVM_StartThread() -> JavaThread() -> os::create_thread() -> pthread_create()

可以发现在 Java 中每次调用 start() 方法,都会在 C++ 的 JavaThread 构造方法里通过 pthread 进行创建一个内核线程。

回到问题 TOP 1 ,也就是说 Java 采用的线程模型是 1:1

1.3 混合实现(M:N)

优点:

  • 用户线程的操作成本低
  • 充分利用硬件
  • 阻塞问题折中处理

缺点:

  • 需要用户层管理和映射

2. 线程状态分析

在状态枚举类 java.lang.Thread.State 中我们看到了一共有 6 种状态

  • NEW

    新建状态,线程还没调用 start() 方法

  • RUNNABLE

    可运行状态,调用 start() 方法后,正在运行或等待系统资源

  • BLOCKED

    阻塞状态,等待锁、Object#wait()-notify() 后都会进入阻塞状态

  • WAITING

    等待状态,调用 Object#wait()、join()、LockSupport#park() ,注意这里都是没有超时时间

  • TIMED_WAITING

    超时等待状态,调用 Thread#sleep()、Object#wait(long)、join(long)、LockSupport#parkNanos、LockSupport#parkUntil,可以发现这里都是有超时时间

  • TERMINATED

    终止状态,线程已经执行完毕

2.1 线程状态流程图

回到问题 TOP 2 ,相信看完这个流程图会对状态流转更清晰一些

3. 核心方法分析

3.1 join()

例如我们 主线程A 要等待 子线程B 完成再往下执行,可以调用 子线程B 的join() 方法

回到问题 TOP 3 ,其实就是通过 wait 方法来让主线程等待,最后子线程完成后会唤醒主线程来实现了一个线程之间的通信。

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
public final void join() throws InterruptedException {
join(0);
}

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");
}
// 默认 join()参数走这个分支
if (millis == 0) {
// 只要线程还未完成
while (isAlive()) {
// 0 是一直等待下去
wait(0);
}
} else {
while (isAlive()) {
// 还剩下多少时间
long delay = millis - now;
// 时间到了就跳出
if (delay <= 0) {
break;
}
// 调用wait方法等待
wait(delay);
// 当前已经过了多久
now = System.currentTimeMillis() - base;
}
}
}

3.2 interrupt()

我们知道 stop() 方法由于太暴力和不安全已经设置为过期,现在基本上是采用 interrupt() 来交给我们优雅的处理。为什么这样说呢?

我们看下面 interrupt() 的实现可以看到实际上只是设置了一个中断标识(通知线程应该中断了),并不会真正停止一个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();

synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}

但是我们可以通过这个中断的通知来自己处理是继续运行还是中断,例如我们想要中断后停止线程:

1
2
3
4
5
6
7
8
9
Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
// do more work.
}
});
thread.start();

// 一段时间以后
thread.interrupt();

需要注意的是,在一些可中断阻塞函数中,会抛出 InterruptedException,需要注意的是如果你不想处理继续往上抛,需要再次调用 interrupt() 方法(因为中断状态已经被重置了)。

4. 总结

我们这篇文章主要是分析了线程模型和线程的状态,已经几个核心方法的实现,相信看完会对线程有了更深一层的认识。