TOP 带着问题来分析
- Java 的线程模型
- Java 的线程状态
- 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 | public final void join() throws InterruptedException { |
3.2 interrupt()
我们知道 stop() 方法由于太暴力和不安全已经设置为过期,现在基本上是采用 interrupt() 来交给我们优雅的处理。为什么这样说呢?
我们看下面 interrupt() 的实现可以看到实际上只是设置了一个中断标识(通知线程应该中断了),并不会真正停止一个线程。
1 | public void interrupt() { |
但是我们可以通过这个中断的通知来自己处理是继续运行还是中断,例如我们想要中断后停止线程:
1 | Thread thread = new Thread(() -> { |
需要注意的是,在一些可中断阻塞函数中,会抛出 InterruptedException,需要注意的是如果你不想处理继续往上抛,需要再次调用 interrupt() 方法(因为中断状态已经被重置了)。
4. 总结
我们这篇文章主要是分析了线程模型和线程的状态,已经几个核心方法的实现,相信看完会对线程有了更深一层的认识。