目录
1.认识多线程
(1)详解多线程
像我们之前写过的所有代码都是只用了一个核心,不论我们如何优化代码,最多也只能用到一个核心,即便是跑满这个核心,其他核心也是空着的;
通过写特殊的代码,把多个CPU核心,都能利用起来,这样的代码就称为“并发编程"
多进程编程,就是一种典型的并发编程.多进程编程的缺陷:当需要频繁的创建和销毁编程时,消耗的时间和空间就会明显的增加,再者有些任务场景需要"等待IO",为了让等待IO的时间能够去做⼀些其他的⼯作,也需要用到并发编程.
因此为了解决进程开发较大的问题,创建了线程,线程可以理解成,更轻量的进程.
也能解决并发编程的问题,但是创建/销毁的开销,要比进程更低.线程概念:一个线程就是一个"执行流".每个线程之间都可以按照顺序执行自己的代码.多个线程之间"同时"执行着多份代码.
因此,多线程的编程,就成了当下主流的并发编程方式.
多线程编程相比多进程线程的优势?
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
(2)进程和线程的区别
所谓的进程,在系统中,是通过PCB这样的结构体来描述,通过链表的形式来组织的
对于系统中,线程,同样也是通过PCB来描述的(Linux)进程和线程的关系:
一个进程,其实是一组PCB一个线程,是一个PCB
一个进程包含了多个线程,此时每个线程,都可以独立的到CPU上调度执行.线程是系统,"调度执行"的基本单位.
进程是系统"资源分配"的基本单位.可执行程序在操作系统中的基本运行过程?
一个可执行程序,运行的时候(双击)操作系统就会创建进程,给这个程序分配各种系统资源(CPU,内存,硬盘,网络带宽..)同时也会在这个进程中,创建出一个或者多个线程.这些线程再去CPU上调度执行.
我们上一篇文章所说的进程调度实际上就是线程调度,只不过时相对于一个线程的进程来说的,如果有多个线程在一个进程中,每个线程,都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在CPU上调度执行.
进程和线程的区别
- 进程是包含线程的.每个进程⾄少有⼀个线程存在,即主线程。
- 同一个进程中的这些线程共用同一份系统资源
- 线程比进程,更轻量,创建线程,省去了“"分配资源"过程.销毁线程,也省去了"释放资源"过程.
当你创建第一个线程后,该线程就会负责分配资源,当再创建线程时就不必再重新分配资源了多个线程在一个进程中,每个线程,都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在CPU上调度执行.
多个线程之间,可能会相互影响.线程安全问题.一个线程抛出异常,也可能会把其他线程也起带走.
多个进程之间,一般不会相互影响.一个进程崩溃了,不会影响到其他进程.(这一点也称为“进程的隔离性")线程数目越多越好吗?怎样进一步提高效率?
- 线程的数目越来越多时效率就不会得到明显的提高,提高效率的关键是充分利用多核心来进行并行执行,但当线程数超出CPU的核心数目时,就无法在微观上完成所有线程的"并行"执行,就会存在严重的"竞争"
多个线程之间,可能会相互影响.线程安全问题.一个线程抛出异常,也可能会把其他线程也起带走.
- 我们可以通过升级核心数,换一个核心数更多的CPU来提高
2.Thread创建线程
线程是操作系统的概念,操作系统提供了一些API可以操作线程,不同的系统API也不同,Java对系统API进行了封装,使用Thread类就可以创建出一个线程.
(1)继承Thread重写run方法
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("hello run");
try {
Thread.sleep(1000);//1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();//回调
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
为什么Thread这个类不需要进行导包?
在java.lang包下的所有类不需要进行导包操作,Thread,String...都不需要导包
代码说明:
- 继承的目的是为了重写该类下的run方法
- 重写run方法中的语句就是要即将创建出的线程,所执行的逻辑
- t.start就是创建线程,调用start就会在进程内部,创建出一个新的线程.新的线程就会执行刚才run里面的代码
run方法没有手动调用就被执行了,类似这种情况实际上是被系统/库/框架进行调用了,此时这种方法就被称为回调函数- 该代码运行起来就是一个进程,进程中包含了两个线程,main方法的线程就是主线程,一个进程至少有一个线程指的就是主线程,t.start就相当于新创建了一个线程,主线程和新线程就会并发的在CPU上执行
run()方法可以进行单独调用,但此时就不算是创建新的线程了- Thread.sleep()表示休眠时间,单位是毫秒,该方法是Thread类下的静态方法,所以直接使用类名调用即可
静态方法可以理解为类方法,它是在编译过程中确定的
动态方法可以理解为实例方法,他是在运行过程中确定的
- 为什么run()方法里使用sleep方法时不能向上抛异常?
此处是重写父类的run
父类的run没有throws这样的异常声明子类,重写的时候也就不能增加throws- 多个线程之间,谁先去CPU上调度执行,这个过程是"不确定的"(不是数学意义的随机)而是这个调度顺序,取决于操作系统,内核里,"调度器"的实现~~
调度器里,有一套规则,但是咱们作为应用程序开发,没法进行干预,也感受不到此时我们可以借助第三方工具jconsole来查看线程的情况,该工具在jdk目录下的bin文件夹下
注意:要在代码运行的情况下打开,不能停止 若没有信息可尝试使用管理员身份打开
一个Java中不止有两个线程
main函数就是主线程,Thread-0就是t.start创建的新线程(代码中自己创建的线程命名的规律就是Thread-数字)
作用:
这些线程都是起到了一些辅助作用
- 垃圾回收
- 统计信息,调试信息
(2)实现Runnable重写run
package thread;
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
代码说明:
- Runnable就是用来描述要执行的任务是什么
通过Thread创建线程,线程要执行的任务,是通过Runnable来描述的,而不是通过Thread自己来描述的- 只有当runnable传到Thread中,runnable才算是一个线程否则就是一个类
好处:使用Runnable这种方法的是更有利于解耦合
(3)匿名内部类实现继承Thread重写run
package thread;
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
代码说明:
- 定义匿名内部类,这个类是 Thread的子类.
- 类的内部,重写了父类的run方法.
- 创建了一个子类的实例.并且把实例的引用赋值给了t .
好处:内聚性更好一些
(4)匿名内部类实现Runnable重写run
通过匿名内部类来实现,本质就是方法二
package thread;
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t = new Thread(runnable);
t.start();
while(true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
//更为简便的方法
package thread;
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
while(true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
(5)基于lambda表达式创建线程
package thread;
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
3.Thread类及常见方法
(1)常见构造方法
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
- 给线程起名字,不起名字默认就是Thread-0,Thread-1
给线程起名字,不会影响到线程的执行效果.但是,起一个合适的名字,有利于调试程序.- ThreadGroup 线程组.
把多个线程,放到一组里.方便统一的设置线程的一些属性.现在用的更多的是
(2)Thread的几个常见属性
package thread;
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("线程ID:" + t.getId());
System.out.println("线程Name:" + t.getName());
System.out.println("线程状态:" + t.getState());
System.out.println("线程优先级:" + t.getPriority());
}
}
注意:可能大家的线程状态会出现RUNNABLE的状态, 是因为Thread-0和main是两个线程,这两个线程是并发执行的,当先执行main线程的打印就是RUNNABLE状态,先执行Thread-0的sleep就是TIMED_WATTING状态
常见属性详解:
- ID:ID是JVM自动分配的,不能手动设置,是Thread对象的身份标识
- 通常情况下,一个Thread 对象,就是对应到系统内部的一个线程(PCB)但是也可能会存在一个情况, Thread对象存在,但是系统内部的线程已经没了/还没创建,也就是new操作执行完创建对象后,但在系统中还没有实际的线程,必须在t.start执行后才会在系统中创建线程
- State状态:是JVM自动分配的,不能手动设置,状态表示了当前线程处于什么状态(阻塞/就绪,Java中实则更为详细)
- Priority优先级:设置不同的优先级,会影响到系统的调度.这里的影响,是基于"统计"规则的影响.直接肉眼观察,很难观察到效果
- 辨析前台线程和后台线程
后台线程/守护线程:在执行过程中不能阻止进程结束(虽然线程在运行,但当进程结束后线程也会随之带走)
我们在main线程中t.start执行创建的线程Thread默认是前台线程
- 如何将线程设置为后台(守护)线程?
package thread; public class Demo8 { public static void main(String[] args) throws InterruptedException { //Thread后台线程 Thread t = new Thread(() ->{ while(true) { System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.setDaemon(true);//设置为后台线程,默认是前台线程 t.start();//启动Thread线程 //main线程中的前台线程,当前台线程结束后后台线程也会被带走 for (int i = 0; i < 3; i++) { System.out.println("hello main"); Thread.sleep(2000); } } }
使用的注意事项:
setDaemon
方法默认是前台线程,只有当需要设置为后台线程时才需要使用该方法setDaemon
方法必须在线程启动(即调用start()
方法)之前调用。否则,会抛出IllegalThreadStateException
异常。- 一旦线程开始运行,就无法更改其守护线程的状态。
- 在守护线程中执行的操作应该尽快完成,因为当所有非守护线程结束时,守护线程会被立即终止,而不会执行完后台线程方法中的剩余任务。
当main前台线程执行完后,Thread线程也会被随之带走,两个线程是并行执行的,不能确定谁先执行谁后执行
- 前台线程:在执行过程中能阻止进程结束
前台线程(例如main主线程,Thread线程)能阻止进程结束,后台线程不能阻止进程结束
isAlive是否存活:用来判断内核线程是否存在,true表示内核的线程存在,false表示内核的线程不存在
代码中,创建的new Thread对象,生命周期,和内核中实际的线程是不一定一样的.
可能会出现,Thread对象仍然存在,但是内核中的线程不存在了这样的情况.(但是不会出现 Thread对象不存在,线程还存在这种)
1)调用start之前,内核中,还没创建线程
2)线程的run 执行完毕了,内核的线程就无了.但是Thread 对象,仍然存在.
此时我们就可以通过isAlive来区分
线程的执行顺序是未知的,当两个线程的sleep时间相同时,执行完sleep后不知道接下来两个线程谁先执行谁后执行package thread; public class Demo9 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() ->{ for (int i = 0; i < 3; i++) { System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println(t.isAlive());//false t.start(); //此处Thread线程启动后不知道先执行main线程中的isAlive语句还是Thread中的打印语句 //当Thread线程先执行则main线程中的isAlive语句就是true //当Thread线程后执行则main线程中的isAlive语句就是false System.out.println(t.isAlive()); Thread.sleep(3000); System.out.println(t.isAlive()); } }
代码解释:
此处Thread线程启动后(t.start)不知道先执行main线程中的isAlive语句还是Thread中的打印语句 当Thread线程先执行则main线程中的isAlive语句就是true 当Thread线程后执行则main线程中的isAlive语句就是false那为什么执行起来一直是true呢?
上述所说的不一定,不是指,双方概率均等.
实际上这里的两种情况的概率,会随着你系统的不同,随着你代码运行环境的不同,都可能存在差异
(3)启动一个线程-start()
调用start方法,才真的在操作系统的底层创建出一个线程.
在Thread类下只有当t.start执行后才算是创建了线程,然后才会去执行run
main方法作为程序的入口不需要用start()
方法启动线程,它会自动运行在一个由JVM创建的主线程中,而你自己创建的线程(Thread)则需要通过调用start()
方法来启动。
4.线程的核心操作
(1)创建线程start()
经典面试题之start和run的区别?
- start的本质是调用系统的API,在系统内核中创建线程(创建PCB,加入链表)
start是调用系统函数,真正在系统内核中创建线程(创建PCB添加到链表中),此处的start会调用不同系统的API(Windows,Linux...不同的系统执行的系统函数是不一样的)
当执行start创建线程之后再去执行run- run描述了线程要执行的任务,也可称为线程的入口
start的执行速度非常快,当start执行完毕后新线程就会开始执行,调用start的线程main线程也会继续执行,即两个线程并发执行
调用start,不一定非得是main线程调用.任何的线程都可以创建其他线程.
但一个Thread对象一次只能调用一次start即一个对象只能对应系统中一个线程,此时是因为第一次调用start时Thread的状态是NEW,调用后状态就会发生改变,只有状态是NEW时才能成功调用
package thread; public class Demo10 { public static void main(String[] args) { Thread t = new Thread(() -> { System.out.println("t"); Thread t2 = new Thread(() -> { System.out.println("t2"); }); t2.start(); }); t.start(); } }
(2)中断线程interrupt()
B正在运行,A想让B结束
其实核心就是A要想办法让B的run方法执行完毕,此时B就自然结束了.而不是B的run 执行一半,A直接把B强制结束了.这样做是为了确保B执行完,避免是一个半成品,这里是让B更加快速的执行完
方法一:
package thread; public class Demo11 { private static boolean isQuit = false; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while(!isQuit) { System.out.println("thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); Thread.sleep(2000); System.out.println("main线程尝试中断t线程"); isQuit = true; } }
为什么将isQuit放到main中就会出现报错呢?
变量捕获问题,isQuit和lambda定义在一个作用域中,此时lambda 内部,是可以访问到lambda外部(和lambda同一个作用域)中的变量的
观察报错信息:
- final:一个变量被声明为
final
意味着它的值在初始化之后就不能再被修改。- effectively final:即便你没有显式地将一个变量声明为
final
,只要它在初始化之后没有被重新赋值,Java编译器也会将其视为effectively final
。这意味着你可以在不显式使用final
关键字的情况下在lambda表达式中使用它。因此我们要写成成员变量的方式,此时进行的就是内部类访问外部类(内部类本身就能访问外部类成员)
lambda表达式,本质上是一个"函数式接口"产生的“匿名内部类"
方法二:
package thread; public class Demo12 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { Thread currentThread = Thread.currentThread(); while (!currentThread.isInterrupted()){ System.out.println("thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); Thread.sleep(2000); t.interrupt(); } }
代码解释:
Thread currentThread = Thread.currentThread();currentThread()是Thread类的静态方法.就能获取到调用这个方法的线程的实例.
哪个线程调用,返回的引用就指向哪个线程的实例.(类似于this) currentThread.isInterrupted()isInterrupted()是Thread类里的一个成员,它的属性是boolean,初始情况下这个变量是false,未被终止,一旦外面的其他线程,调用一个interrupt方法,就会设置上述标志位.
这里的currentThread不能改为t?
因为这里的执行顺序是先创建了一个匿名内部类,再执行内部类里的语句时,t线程还没有创建,也就是说下列的Lambda的代码在编译器眼里,出现在Thread t 的上方的.出时 t还沿有被定义的 t.interrupt();在主线程中控制t线程被终止,也就是设置标识位我们发现当我们运行的时候会抛出RuntimeException异常信息
原因:
由于isInterrupt()判断语句和打印语句执行速度很快,因此整个循环基本上都在执行sleep(1000),当main线程调用interrupt时,大概率t线程正处于休眠状态
此处Interrupt不仅仅能设置标志位,还能把刚才这个sleep 操作,给唤醒
比如, sleep 此时刚睡了100ms,还剩900ms,此时Interrupt被调用了
此时sleep就会直接被唤醒,并且抛出InterruptedException异常由于catch 中默认代码再次抛出异常,再次抛出的异常
没人catch,最终就到了JVM这一层,进程就直接异常终止了.
当我们将catch语句改成简单的sout打印语句时发现虽然不抛出异常了但并没有进行中断操作,标志位好像没设置一样,一直在执行,明明此时的interrupt把sleep唤醒了,异常也被catch住了,那原因是什么呢?
首先标志位肯定是设置了的只是sleep等阻塞的函数被唤醒之后,就会先清空刚才设置的interrupted标志位,因此想要结束线程就需要在catch中return/break
这里我们通过一个简单的例子来理解这种中断操作
Java中,终止线程,是一个"温柔"的过程,不是强行就终止了
我和女朋友正在看电视,突然女朋友跟我说她渴了让我去帮他买瓶水,此时我可以做出三种选择:
- 无视女朋友,继续看电视
B线程直接无视A线程,catch中什么也不做,B线程就会继续运行,对应代码就是sleep清楚标志位使B继续执行,如果sleep不清楚标志位的话,B就会结束,无法继续执行代码- 停止看电视,立即去买水
如果B线程想立即结束,就直接在catch 中写上return 或者break.此时,B线程就会立即结束.
- 看完这一集再去买水
如果B想稍后再结束,就可以在catch中写上一些其他的逻辑(比如释放资源,清理一些数据,提交一些结果..…收尾工作)这些逻辑完成之后,再进行return / break;
Java中,终止线程,是一个"温柔"的过程,不是强行就终止了
A希望B线程终止,B收到这样的请求之后,B需要自行决定,是否要终止/立即还是稍后(B线程内部的代码来决定的,其他线程无权干涉
(3)等待一个线程join()
线程等待:在操作系统中针对多个线程的执行,是一个随即调度,抢占式执行的过程
线程等待,就是在确定两个线程的"结束顺序"
无法确定两个线程调度执行的顺序,但是可以控制,谁先结束,谁后结束.
让后结束的线程,等待先结束的线程即可.
此时后结束的线程就会进入阻塞,一直到先结束的线程,真的结束了,阻塞才解除
t等main:public class Demo14 { public static void main(String[] args) { Thread t = new Thread(()->{ for (int i = 0; i < 3; i++) { System.out.println("t开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t结束"); }); t.start(); System.out.println("main开始"); try { t.join(); //main等待t t执行完再执行main } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main结束"); } }
main等t:
在执行主线程的内容时,加上等待时间,确保t线程执行完毕
此时join并没有发生阻塞,t线程已经结束了
join就是确保,被等待的线程,能够先结束.
如果已经结束了, join就不必再等了public class Demo15 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ for (int i = 0; i < 3; i++) { System.out.println("t开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t结束"); }); t.start(); Thread.sleep(4000); //此处时间必须大于for循环整体的时间,这样才能保证t线程执行完再执行main线程 System.out.println("main开始"); try { t.join(); //main等待t t执行完再执行main } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main结束"); } }
t2等t1:
必须在t2执行前来设置t.join()
a线程中调用b.join就是a等待b结束public class Demo17 { public static void main(String[] args) { Thread t1 = new Thread(() -> { for (int i = 0; i < 2; i++) { System.out.println("t1开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t1结束"); }); Thread t2 = new Thread(() -> { try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 3; i++) { System.out.println("t2开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2结束"); }); System.out.println("main开始"); t1.start(); t2.start(); } }
以上操作中使用到的join操作都是无参的版本,无参数,意思是"死等"
被等待的线程,只要不执行完,这里的等待,就会持续阻塞
(4)获取当前线程currentThread()
实现t线程等待main线程//t线程等待主线程 public class Demo18 { public static void main(String[] args) { Thread mainthread = Thread.currentThread();//获取main线程 Thread t = new Thread(() -> { try { mainthread.join();//t等待main } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 3; i++) { System.out.println("t开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t结束"); }); System.out.println("main开始"); t.start(); System.out.println("main结束"); } }
(5)休眠当前线程sleep()
Thread.sleep让调用的线程阻塞等待是有一定时间的,当线程执行sleep时就会使这个线程不参与cpu的调度,从而把cpu的资源让出来给别人使用,也叫“放权”操作
某个线程cpu占用率过高时就可以通过sleep来进行改善
5.线程的状态
- NEW(初始状态):当前Thread对象虽然有了,但是内核的线程还没有(还没调用过start)
- TERMINATED(终止状态):当前Thread对象虽然还在,但是内核的线程已经销毁(线程已经结束了)
- RUNNABLE(就绪状态):正在cpu 上运行+随时可以去cpu上运行
- WAITING(等待状态):没有超时时间的阻塞等待,join / wait
- TIME_WAITING(超时等待) :有超时时间的等待,比如sleep,或者join带参数版本.....
- BLOCKED(阻塞状态):由于某种原因导致正在运行的线程放弃了CPU资源并且暂停了执行,此时线程进入了阻塞状态。
public class Demo19 { public static void main(String[] args) throws InterruptedException { //WAITING Thread mainThread = Thread.currentThread(); Thread t = new Thread(() -> { while (true){ try { System.out.println(mainThread.getState()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); t.join(); //System.out.println(t.getState()); } public static void main4(String[] args) throws InterruptedException { //TIMED_WAITING Thread t = new Thread(() -> { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); Thread.sleep(500); System.out.println(t.getState()); } public static void main3(String[] args) throws InterruptedException { //RUNNABLE Thread t = new Thread(() -> { while (true){ } }); t.start(); System.out.println(t.getState()); } public static void main2(String[] args) throws InterruptedException { //TERMINATED Thread t = new Thread(() -> { }); t.start(); Thread.sleep(1000); System.out.println(t.getState()); } public static void main1(String[] args) { //New Thread t = new Thread(() -> { for (int i = 0; i < 2; i++) { System.out.println("t开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t结束"); }); System.out.println(t.getState()); t.start(); } }