注意:Java 中只有 Thread 类的实例才能代表一个 JVM 中的线程,Runnable/Callable 之类的都不是。
所以使用 jstack 可以看到,所有 Java 线程的方法栈底层,只有 main() 方法或者 Thread.run() 方法。(Native 线程除外)
生命周期:
注意:退出 waiting 状态后,是先进入 blocked 状态。
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 种方式:
- 继承 Thread,重写 run() 方法
- 实现 Runnable 接口的 run() 方法,然后使用 new Thread().start() 或者 ThreadPoolExecutor.execute() 或 submit() 来运行
- 实现 Callable 接口的 call() 方法,然后调用线程池的 submit() 方法提交给线程池
不能多次启动线程:
- 线程生命周期中,一旦线程处于终止状态,就不能重新回到其他状态
- 会抛出 IllegalThreadStateException
Thread 的底层模型:
Thread 类的每一个实例代表一个 JVM 中的线程:
- 在 Linux 上称为“轻量级进程”,和进程无本质区别
- Java 会使用 fork 系统调用来创建线程
- 在 Windows 上使用系统线程
线程与进程的区别(Linux):
Linux 下,每个进程和线程都是一个 Task,而同一个进程的线程,会指向同一片内存空间,即进程有独立的地址空间,而线程是共享的。
- pstree 可以查看进程树,也可看到进程对应的子进程和线程:
- -p 命令可以线程线程的 pid,否则只会显示个数
ps -Lf 可以看到具体的子进程和线程的信息:
- -L:显示 LWP、NLWP
- -f:显示 UID、PPID
- PID:进程 ID
- PPID:父进程 ID
- LWP:轻量级进程 ID,即线程
- NLWP:线程个数
wait 和 notify 流程图:
问题:
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 不会释放锁对象