注意:Java 中只有 Thread 类的实例才能代表一个 JVM 中的线程,Runnable/Callable 之类的都不是。
所以使用 jstack 可以看到,所有 Java 线程的方法栈底层,只有 main() 方法或者 Thread.run() 方法。(Native 线程除外)

生命周期:

注意:退出 waiting 状态后,是先进入 blocked 状态。
image.png

image.png
Thread - 图3

runnable:

  • 可运行状态,至于有没有运行要看 CPU 调度
  • IO 阻塞状态

注意:与进程的生命周期不同,线程的阻塞状态仅仅是指获取锁的时候的状态,IO 阻塞不属于 blocked。而进程处于 IO 阻塞的时候,也是属于阻塞状态。

blocked:

  • 专门指代等待获取锁的状态,只与 synchronized 相关。
  • Thread state for a thread blocked waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait()
  • 处于 blocked 状态的线程,会被放在锁对象的同步队列中 SynchronizedQueue。

waiting:

  • wait 和 nofity 机制是 JVM 自己设计的,用于多线程协同。
  • 处于 waiting 状态的线程,会被放在锁对象的等待队列中 WaitQueue。
  • 调用以下方法,可以让一个线程进入 waiting 状态:
    • Object.wait()
    • Thread.join()
    • LockSupport.park()
  • 唤醒线程:
    • Object.notify()
    • Object.notifyAll()
  • Object.wait() 必须在同步块中调用,需要在获取锁对象之后,才能将自身加入锁的等待队列
    • 否则会抛出 IllegalMonitorStateException
    • 调用后,当前线程会释放锁

time_waiting:

  • Thread.sleep()
  • Object.wait()
  • Thread.join()
  • LockSupport.parkNanos()
  • LockSupport.parkUntil()

terminated:

  • 代码运行完,程序正常结束
  • 使用退出标识,在程序中退出
  • 抛出异常,没有被 catch。包括被 interrupt 的异常
  • 调用 stop 方法。(线程不安全,会产生不可预料的结果,不推荐使用)

操作系统线程的生命周期:

JVM 线程的 Runnable 和 Linux 线程的 Sleep:https://zhuanlan.zhihu.com/p/85661193
源码枚举对比:https://www.cnblogs.com/mazhimazhi/p/12289791.html

JVM:new、runnable、wait/time_wait、blocked、terminal
Linux:new、runnable、running、sleep/blocked、terminal

操作系统中,当线程调用阻塞 API 时,会进入 blocked 状态
比如:IO 文件操作
JVM 线程中,blocked 只表示锁状态,所以 IO 阻塞状态,线程处于 runnable 状态
比如 socket.accept(),jstack 看到是 Runnable,而使用 top -H 查看,却是 S 状态。

JVM 线程中的 waiting 状态,也是对应操作系统中线程的休眠/阻塞状态

创建线程的 3 种方式:

  1. 继承 Thread,重写 run() 方法
  2. 实现 Runnable 接口的 run() 方法,然后使用 new Thread().start() 或者 ThreadPoolExecutor.execute() 或 submit() 来运行
  3. 实现 Callable 接口的 call() 方法,然后调用线程池的 submit() 方法提交给线程池

不能多次启动线程:

  • 线程生命周期中,一旦线程处于终止状态,就不能重新回到其他状态
  • 会抛出 IllegalThreadStateException

Thread 的底层模型:

Thread 类的每一个实例代表一个 JVM 中的线程:

  • 在 Linux 上称为“轻量级进程”,和进程无本质区别
    • Java 会使用 fork 系统调用来创建线程
  • 在 Windows 上使用系统线程

线程与进程的区别(Linux):

Linux 下,每个进程和线程都是一个 Task,而同一个进程的线程,会指向同一片内存空间,即进程有独立的地址空间,而线程是共享的。

  • pstree 可以查看进程树,也可看到进程对应的子进程和线程:

image.png

  • -p 命令可以线程线程的 pid,否则只会显示个数

image.png
ps -Lf 可以看到具体的子进程和线程的信息:

  • -L:显示 LWP、NLWP
  • -f:显示 UID、PPID
  • PID:进程 ID
  • PPID:父进程 ID
  • LWP:轻量级进程 ID,即线程
  • NLWP:线程个数

image.png

wait 和 notify 流程图:

image.png

问题:

Q:为什么 wait/notify 要设计到 Object 类而不是 Thread 类中?

A:线程间是通过条件来协同的,wait/notify 是为了实现控制代码流程,而不是去控制特定线程的行为。
比如生产者和消费者模型,当队列为空的时候,消费者线程就会 wait;当一个生产者生产了,就会使用 notify 来唤醒消费者线程。
使用 wait/notify 实现生产者消费者模型:https://blog.csdn.net/u014039577/article/details/52243116

Q:为什么 wait/notify 要配合 synchronized 使用?

  • A使用它们一般都是要先判断条件的,没有用 synchronized 的情况,如果在满足条件后准备 wait 的时候,CPU 时间片用完,轮到 notify 的线程执行,它先 notify ,此时没有线程处于 waiting 所以没有线程被唤醒,接着 CPU 回到 wait 的线程执行 wait,最终导致 wait 线程一直在 wait。
  • 如果没获取锁调用 wait/notify,会抛出 IllegalMonitorStateException

https://blog.csdn.net/lengxiao1993/article/details/52296220

Q:wait 和 sleep 的区别?

  • wait 属于 Object 类,而 sleep 属于 Thread 类
  • wait 需要获取锁对象之后才能调用,而 sleep 不需要
  • wait 会释放锁对象,而 sleep 不会释放锁对象