1、 OS线程的生命周期(五种状态)
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)
- 新建:创建线程对象,但它此时不能运行,与其他java对象一样,只是java虚拟机为它分配了内存,没有表现出任何线程的动态特征
- 就绪:线程对象调用start()方法,就处于就绪状态;处于就绪状态的线程位于线程队列中,此时只是具备运行的条件,能否获得CPU使用权并运行,还要看系统调度
- 运行:获得CPU使用权,并开始执行run()方法体;该线程可能不会一直处于运行,当它使用完系统分配的时间后,系统就会剥夺线程占用的CPU资源,让其他线程执行。注意:只有处于就绪的线程才能转到运行状态。
- 阻塞:一个线程在某些特殊情况下,会让出CPU执行权,并暂时中止自己的执行,进入阻塞。进入阻塞后,就不能进入排队队列,只有引起阻塞的原因消除后,再进入就绪。阻塞分为三种:
- 等待阻塞:运行状态中的线程执行
- 同步阻塞:线程在获取synchronized同步锁失败(该锁被其他线程占用),JVM会把该线程放入锁池(lock pool)中,线程进入同步阻塞状态
- 其他阻塞(超时等待):通过调用线程的
- 死亡:线程调用stop方法或正常执行完毕或抛出一个未捕获异常/错误
(1)运行状态-------->死亡 :线程执行完毕、stop()方法、出现异常或错误没处理
阻塞不是线程的最终状态,死亡才是。线程一直阻塞是有问题的
(2)运行----------->阻塞:sleep()、join()、等待同步锁、wait()方法、suspend()(挂起,已过时)
(3)阻塞----------->就绪:sleep()时间到、join()执行完、获取到同步锁、notify()方法/notifyAll()、resume()方法(对应suspend)
注意:wait()、notify()、notifyAll()方法都是Object类的方法而不是Thread和Runnable方法
2、Thread类的源码中的线程状态
2.1 start与run方法
- start()方法:
- 使该线程开始执行;Java虚拟机调用该线程的run方法,用类直接调用run()是开启不了线程的。
- 结果是两个线程同时运行:当前线程(从对start方法的调用返回)和另一个线程(执行其run方法)。多次启动一个线程是不合法的。特别是,线程一旦完成执行,就不能重新启动。(所以线程池需要 for 循环中使用?)
- 不能重复执行start方法,报错:java.lang.IllegalThreadStateException非法的线程状态异常。线程执行完毕,变成终止状态,不可能再次变成就绪,因此抛出异常
public static void main(String[] args) {
Thread t = new Thread(new RunnableTest());
t.start();
t.start(); // 错误 ,java.lang.IllegalThreadStateException
}
- start方法调用后线程是否立即执行?
- 线程不是马上执行的;
- 准确来说,调用start( )方法后,线程的状态从 new 的状态 变成 “READY(就绪)”状态,而不是“RUNNING(运行中)”状态。线程要等待CPU调度,不同的JVM有不同的调度算法,线程何时被调度是未知的。因此,start()方法的被调用顺序不能决定线程的执行顺序。
- 为什么run()方法不是启动线程的方式?
- 如果是实现Runnbale接口的方式创建线程,那么此处的run方法就会传入target
- 如果是继承Thread类的方式创建线程,那么run方法会被完全重写,即实际调用的是子类的重写的run方法
// Thread的run方法是实现的Runnable接口的run方法的
@Override
public void run() {
if (target != null) {
target.run();
}
}
// 通过源码可以看出, 此方法完全没有创建线程的代码. 只是一个普通的方法而已.
// 不会像start方法, 去调用native方法, 去启动线程.
// 下面代码中是用哪个对象来调用run()方法的,是线程object(t1)还是我传入的runnableobject(obj)?
public class A implements Runnable {
public void run() {
System.out.println("....");
}
public static void main(String[] args) {
A obj = new A();
Thread t1 = new Thread(obj);
t1.start();
}
}
根据Thread的run()方法,当实现Runnbale接口的方式创建线程时,实际调用run的时t1即Thread对象
2.2 源码
public enum State {
/**
* Thread state for a thread which has not yet started.
* 程序开始执行到 new的过程都是NEW状态;Thread t = new Thread()
*/
NEW,
/**
* 包含两种状态:可执行(线程可以执行,但没有执行权;即就绪状态) 和 正在执行
* 触发条件:Thread.start()
*/
RUNNABLE,
/**
* 1. 等待监视器锁定的被阻止线程的线程状态。
* 2 .处于阻塞状态的线程正在等待监视器锁定(线程被阻塞等待获取锁);获取到锁,进入Runnable
* 3. 触发条件:synchronized 和 lock.lock() Object.wait()
*/
BLOCKED,
/**
* 1. 触发条件:Object.wait() hread.join() LockSupport.park()
* 无限期等待的的线程(除非主动唤醒)
*/
WAITING,
/*
1. 限期等待
2. 系列方法:都有重载方法:
Thread.sleep(long)
Object.wait(long)
Thread.join(long)
3. 没有重载,有park开头的方法
LockSupport.parkNanos
LockSupport.parkUntil
4. Condition.await():await开头的方法
以上方法执行完的结果:要么是等待超时、要么被唤醒、要么被中断
*/
TIMED_WAITING,
TERMINATED;
}
// 获取当前线程状态的方法
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
2.3 具体说明
public class ThreadStateTest {
public static void main(String[] args) {
ThreadState t = new ThreadState();
synchronized (ThreadState.class){
t.start();
System.out.println(t.getState());//可能是RUNNABLE,可能是BLOCKED;看子线程执行的快慢
try {
// 不释放锁资源;wait()是释放锁资源的
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());
}
}
}
class ThreadState extends Thread{
@Override
public void run() {
synchronized (ThreadState.class){
System.out.println("子线程");
}
}
}
结果:RUNNABLE是就绪状态,因为没有锁资源,之后进入blocked状态
RUNNABLE
BLOCKED
子线程
public class ThreadStateTest {
public static void main(String[] args) {
ThreadState t = new ThreadState();
t.start();
try {
// 不释放锁资源
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (ThreadState.class){
System.out.println(t.getState());
// 注意不是t.notify(),谁是锁,谁wait()
ThreadState.class.notify();
}
}
}
class ThreadState extends Thread{
@Override
public void run() {
synchronized (ThreadState.class){
System.out.println(Thread.currentThread().getState());
System.out.println(this.getState()+":this");
try {
// 注意不是直接wait(),谁是锁,谁wait()
ThreadState.class.wait();
System.out.println(Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
RUNNABLE
RUNNABLE:this
WAITING
RUNNABLE
注意:wait和notify/notifyAll必须放在synchronized里,而且必须是锁调用wait、notify方法:
- 无论是执行对象的必须保证当前运行的线程取得了该对象的控制权(monitor)
- 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException 异常。
- JVM 基于多线程,默认情况下不能保证运行时线程的时序性,可以通过join实现线程顺序的控制
https://blog.csdn.net/zhaoyanjun6/article/details/119645679
3、线程间的关系
对于cpu来说,其实不存在主线程和子线程之分,都是一个线程。
3.1 java中的主线程和子线程:
注意事项:java中main线程是主线程名,和main方法没有关系,只是同名而已,main方法只是程序入口
- 正常情况下,主线程启动了子线程,主线程、子线程各自执行,彼此不受影响。
- 情况2:需求是主线程执行结束,由主线程启动的子线程都结束:将子线程设置为主线程的守护线程
- 情况3:需求是子线程执行结束,主线程等待启动的子线程都结束之后再结束:将子线程join进主线程,也就是暂时让主线程阻塞,换子线程执行
- 实际上,我们对Thread类没有什么控制权,我们几乎不能设置线程的任何状态,我们只能创建任务,并通过某种方式使用线程驱动这个任务
- 所以,在编写多线程代码的时候,遵循规则就变得非常重要。
3.2 用户线程和守护线程
- java中线程分为两种类型:用户线程和守护线程;
- 通过Thread.setDaemon(false)设置为用户线程;
- 通过Thread.setDaemon(true)设置为守护线程;将一个用户线程设置为守护线程的方式是在线程启动后线程对象的setDaemon方法。
- 如果不设置此属性,默认为用户线程。
- 用户线程和守护线程的区别:
- 主线程结束后用户线程还会继续运行,JVM存活;主线程结束后守护线程和JVM的状态又下面第2条确定。
- 如果没有用户线程,都是守护线程,那么JVM结束(随之而来的是所有的一切烟消云散,包括所有的守护线程)。
- 守护线程的特性:
- 守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。
- 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
- 举例:垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
- 生命周期:
- 守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
- 也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”
- 当JVM中所有的线程都是守护线程的时候,JVM就可以退出了,即没有用户线程了;守护线程是服务与用于线程,JVM退出,即便有守护线程未完成,如果还有一个或以上的非守护线程则JVM不会退出。
4、wait()、notify()和sleep()
4.1 wait和sleep对比
- 相同点:都会使线程进入阻塞
- 不同点:
- 两个方法的声明的位置不同:Thread类中声明的sleep,object类中声明wait方法
- 调用的要求不同:sleep可以在任何场景下调用,wait方法必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,它只是睡眠一段时间,但是仍然把持着锁,等待时间结束后继续执行,而wait会直接释放掉锁资源。
- 注意:
- 当sleep结束指定休眠时间后,这个线程不一定立即执行,因为此时其他线程可能正在运行
- notify或者notifyAll方法 是随机唤醒,若是被其他线程抢到了cpu执行权,该线程会继续进入等待状态
4.2 wait必须放在synchronized的同步代码中的原因
- 调用对象的wait()和notify()方法,需要先取得对象的控制权
- 可以使用synchronized (object)来取得对于 object 对象的控制权
- 由于锁对象可以是任意对象,所以wait方法必须定义在Object类中,因为Object类是所有类的基类
- Class 类也默认继承 Object 类,所以 类.class 的对象作为锁也是可以的
public class Util {
public void run(){
Object ob = new Object();
new Thread(new Runnable() {
@Override
public void run() {
try {
ob.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
结果:
java.lang.IllegalMonitorStateException: object not locked by thread before wait()
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:442)
at java.lang.Object.wait(Object.java:568)
at com.example.myapplication.Util$1.run(Util.java:20)
at java.lang.Thread.run(Thread.java:929)
public class Util {
public void run(){
Object ob = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (ob){
try {
//锁.wait()
ob.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
4.3 wait()和notify()
https://blog.csdn.net/x541211190/article/details/109258676
5、操作系统(OS)线程与Java线程
- OS线程:
- Java的线程实际上是对应了操作系统的一个线程:
- 而操作系统实现线程有三种方式:
- 内核线程实现
- 用户线程实现
- 用户线程加轻量级进程混和实现
参见 «深入理解 JAVA虚拟机»第二版 第12章 Java内存模型与线程(378页)
- java线程
- JDK1.2之前是基于用户线程实现的,JDK1.2 中,线程模型替换为基于操作系统原生线程模型实现
- 目前JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了java虚拟机的线程是怎末映射的,这点在不同平台上没法达成一致