三、Thread类
1.常用构造方法
- 代码演示Demo6:自定义线程名称
package thread; public class Demo6 { public static void main(String[] args) { Thread t = new Thread(() -> { while(true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } },"自定义线程"); t.start(); } }
- Q1:使用jconsole查看有哪些线程的时候为啥没有main线程
- 首先mian线程是一定存在的
- 只是main线程执行完了,t方法执行结束,也查看不到t线程
- Q2:main线程需要重写run方法吗
- 不需要,但是main线程是JVM中通过C++代码创建出来的,没有通过Thread类创建,也就不会重写run方法了
- Q1:使用jconsole查看有哪些线程的时候为啥没有main线程
2.Thread的⼏个常⻅属性
- ID:和PCB的id不一样
- 状态:(重点:就绪 + 阻塞)
- 优先级:仅供参考,还是系统调度决定的
- 是否是后台线程(守护线程)
- 前台线程:这样的线程如果不运行的结束的话,Java进程不一定会结束(大佬)
后台线程:所有非后台线程结束,Java进程才结束(小透明)
- Java线程默认都是前台线程,setDaemon方法可以把线程设置为后台线程
- 代码示例Demo7
package thread; public class Demo7 { public static void main(String[] args) { Thread t = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); //设置为后台线程 t.setDaemon(true); t.start(); } }
- Q1:为什么啥什么都没打印/只打印了一次
进程中,只有main是前台线程,main一结束,整个进程就结束了,t没打印或者才打印一个(抢占式执行),整个进程就结束了(最后一个前台线程结束,那么进程也结束)
- Q2:如果start和setDaemon交换顺序,结果会怎样
会报错,关于线程的各种属性设置,都得放到start之前,一旦线程启动,再设置就来不及了
- 代码示例Demo7
- 后台线程举例:gc线程,gc要周期性持续性执行,找垃圾是否需要回收,不可能主动结束
- 前台线程:这样的线程如果不运行的结束的话,Java进程不一定会结束(大佬)
- 5)内核线程PCB是否存活
- Thread对象的生命周期和PCB的声明周期是不完全一样的
PCB是内核中线程的体现;Java中通过Thread对象抽象的表示线程的概念- 创建过程:
- 这个代码Thread对象(已经创建了Thread实例)已经出现,但是PCB还没出现
start才真正创建线程,PCB才出现
- 这个代码Thread对象(已经创建了Thread实例)已经出现,但是PCB还没出现
- 消耗过程:
- 线程(PCB销毁)先结束,但是Thread还在
由于t线程瞬间结束,线程和PCB先被消毁,但是在sleep结束之前,t引用指向的Thread对象,仍是存在的
- 线程(PCB销毁)先结束,但是Thread还在
- PCB还存活,但是Thread对象以及被回收了
线程还没结束,t指向的对象就被GC回收了
- 创建过程:
- Thread对象的生命周期和PCB的声明周期是不完全一样的
3.Thread核心操作
-
start方法
- 作用:启动线程,核心就是是否真的创建线程出来,每个线程都是独立调度执行的(相当于整个程序中多了一个执行流)
- 注意:一个Thread对象,只能start一次
要想再搞一个新的线程,就需要创建另一个Thread对象
-
中断线程
- 作用:其他线程只能提醒一下线程t是不是要终止,至于要不要终止,还是得t线程自己决定(t线程正在执行时)
因为线程的调度是随机的,如果线程t正在做一个很重要的任务,强制终止可能会出bug
- 实现方式:
- (1)自己实现控制线程结束的代码
核心思路:需要终止线程的入口方法尽快执行结束(跳出循环/return都可以)
- (1)自己实现控制线程结束的代码
- 作用:其他线程只能提醒一下线程t是不是要终止,至于要不要终止,还是得t线程自己决定(t线程正在执行时)
package thread;
public class Demo10 {
private static boolean isRunning = true;
public static void main(String[] args) throws InterruptedException {
//boolean isRunning = true;
Thread t = new Thread(() -> {
while (isRunning) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t线程 已经结束");
});
t.start();
Thread.sleep(3000);
//3s之后,主线程修改isRunning的值,从而通过t结束
System.out.println("控制t 线程结束");
isRunning = false;
}
}
-
-
-
- 最终决定权在t代码手里
- Q1:把boolean isRunning = true;放到main函数内部会不会报错
- 编译阶段就过不了:变量捕获:作为lambda表达式/匿名内部类,都能捕获到外面一层作用域中的变量名,就可以使用的(为了保证数据的一致性和避免数据竞争问题,Java 要求这些变量是有效最终变量,即final/”事实“final)
- (2)Thread提供的interrupt/isInterruptted方法
实际上就是Thread里内置了一个标志位,功能更强大- 引入:Demo10如果t线程sleep10s,main线程就无法
- 代码示例:Demo11:
- Thread.currentThread()方法:获取到当前线程(在哪个线程代码中用这个方法就是当前线程),获取t这个引用
lambda表达式早就创建好了,所以t不能在lambda内部使用,编译不了
- isInterrupted()方法:
true:线程要终止;false:线程要继续执行
- t.interrupt()方法
- (1)设置标志位为true
提醒线程要结束了
- (2)唤醒sleep阻塞方法
跟自己实现终止不同,interrupt可以让sleep抛出InterruptedException异常,不会再等待,立即唤醒
- Q1:为什么打印完异常信息,t线程还在继续执行
sleep在搞鬼:当有sleep+触发interrupt的时候线程正在sleep,那么线程被唤醒的同时,就会清除标志位(又变成了false)- 为什么sleep要清除标志位?
目的就是把控制权转交给程序员自己
- 为什么sleep要清除标志位?
- (1)设置标志位为true
- Thread.currentThread()方法:获取到当前线程(在哪个线程代码中用这个方法就是当前线程),获取t这个引用
-
-
-
join方法:线程等待
- 引入:为啥要线程等待:为了确定线程结束的先后顺序
由于抢占式执行,哪个线程被调度不确定,多个线程谁先结束也不确定
- 作用:main线程中调用t.join就是让main等待t【t先结束,main后结束】(只是结束的时候等,开始不用等)
join是不会影响谁先开始的,而且main线程不能被join- eg:代码中有main线程,又有t1和t2,此时main调用t1.join,main阻塞,t1和t2都正常执行;如果是t1调用t2.join,就是t1阻塞,t2和main正常执行
在哪个线程中调用join,谁就阻塞
- eg:代码中有main线程,又有t1和t2,此时main调用t1.join,main阻塞,t1和t2都正常执行;如果是t1调用t2.join,就是t1阻塞,t2和main正常执行
- 代码示例:Demo12
package thread; public class Demo12 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("thread end"); }); t.start(); for (int i = 0; i < 5; i++) { System.out.println("hello main"); Thread.sleep(1000); } t.join(); System.out.println("main end"); } }
- join的情况(t1.join得在t1.start之后)
main调用join,t线程的情况下- (1)如果线程t已经结束,join就会立即返回
- (2)如果t线程还没结束,join就会阻塞等待,一直等到t线程结束之后,join才能解除阻塞,继续执行
阻塞:该线程暂时不参与CPU调度
- 代码示例:Demo13主线程调用多个join
package thread; public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread t2 = new Thread(() -> { for (int i = 0; i < 4; i++) { System.out.println("t2"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread t1 = new Thread(() -> { //t1一进来,就先等t2结束 try { //这里加sleep->保证t2已经start Thread.sleep(1000); t2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } for (int i = 0; i < 3; i++) { System.out.println("t1"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("main end"); } }
- 先执行t1.join,如果t1还没结束,main就等t1结束(此时和t2没有关系),t1结束后,main执行t2.join,main再等待t2结束
其实两个join顺序无所谓,都是main等待t1+t2时间
- 先执行t1.join,如果t1还没结束,main就等t1结束(此时和t2没有关系),t1结束后,main执行t2.join,main再等待t2结束
- 【主流方式】join(时间):加入等待的最大时间,超过这个时间,就不等了
防止出现等待的线程挂了,出现死等的情况- 代码示例:Demo14
package thread; public class Demo14 { public static void main(String[] args) throws InterruptedException { Thread t = Thread.currentThread(); System.out.println(t.getName()); Thread t2 = new Thread(() -> { try { t.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("t2结束"); }); t2.start(); for (int i = 0; i < 5; i++) { Thread.sleep(1000); } System.out.println("main结束"); } }
main等3s就不等了(结果还是t最后一个结束)
- 代码示例:Demo14
- 引入:为啥要线程等待:为了确定线程结束的先后顺序
-
currentThread方法
- 获取到当前线程(在哪个线程代码中用这个方法就是当前线程),获取t这个引用
- static方法:不需要对象,类直接调用
-
sleep方法
- 让当前线程主动进入“阻塞”状态,主动放弃在CPU上的执行,时间到了,才会解除,重新被调度到CPU上执行
native方法:本地方法,JVM实现,通过C++代码实现的
- 注意:sleep控制的是”线程休眠的时间“,而不是”两个代码之间的间隔时间“
- sleep设置的时间,是线程阻塞的时间:1000ms之内,线程一定不会去CPU上执行,当时间到了,线程从阻塞状态恢复到就绪状态,不代表线程立即就能去CPU上执行
- sleep设置的时间,是线程阻塞的时间:1000ms之内,线程一定不会去CPU上执行,当时间到了,线程从阻塞状态恢复到就绪状态,不代表线程立即就能去CPU上执行
- interrupt中断会清除标记位
- 让当前线程主动进入“阻塞”状态,主动放弃在CPU上的执行,时间到了,才会解除,重新被调度到CPU上执行
4.🔥【面试题】线程的状态(PCB的状态)
- NEW
Thread对象有了,还没调用start,系统内部的线程还没创建- 代码示例:Demo17
package thread; public class Demo17 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { System.out.println("hello"); }); System.out.println(t.getState()); t.start(); //使用等待,确保t执行完毕,再来获取t的状态 t.join(); System.out.println(t.getState()); } }
- 代码示例:Demo17
- TERMINATED
线程已经终止,内核中线程已经销毁,但是Thread对象还在 -
- RUNNABLE
就绪状态:这个线程”随叫随到“- (1)这个线程正在CPU上执行
- (2)这个线程虽然没在CPU上执行,但是随时可以调度到CPU上执行
- 阻塞
- WAITING
死等 进入的阻塞- join无参数版本就是
- 代码示例:Demo18
package thread; public class Demo18 { 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) { throw new RuntimeException(e); } } }); t.start(); //死等版本的join //t.join(); //带有时间的等 t.join(3000); } }
- TIMED_WAITING
带有超时时间的等- join有参数版等待的哪个线程的状态
- 拓展:后续发现某个线程卡死了,就需要关注线程的状态,看线程是到哪一行卡住了(阻塞)
- BLOCKED
进行锁竞争的时候产生的阻塞
- WAITING
- 线程转换
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论(0)