Thread类是JVM用来管理线程的一个类,每个线程都有唯一的一个Thread对象与之关联。JVM使用一些属性描述线程对象,用一些类方法管理对象。
目录
一、线程的属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否为后台线程 | isAlive() |
是否存活 | isDaemon() |
是否中断 | isInterrupted() |
- 线程的ID
获取到的是线程在JVM中的身份标识,是线程的唯一标识,不同线程不会重复
注意理解“唯一”的含义。
内核的PCB、用户态的线程库、JVM中,只要用到线程且需要对其进行管理的地方,都会对线程进行身份标识。三个标识各不相同,但目的一样。每个地方的身份标识都是唯一的。
- 线程的名称
在Thread构造方法里取的名字
- 线程的状态
获取线程当前处于就绪还是运行等其他状态,是JVM当中设立的状态
(JVM中的线程有6种,具体类别以及状态转换会在另一篇博客中总结~ )
- 线程优先级
获取到线程调度的优先级
获取 ID、名称、状态、优先级代码示例:
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(true){
for(int i=0;i<10;i++){
System.out.println("新线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
System.out.println("线程创建前状态: "+thread.getState());
thread.start();
//Thread.sleep(500);
System.out.println("线程创建后状态: "+thread.getState());
System.out.println(thread.getPriority());
System.out.println( thread.getName());
System.out.println(thread.getId());
}
}
- 线程是否为后台线程
线程分为前台线程和后台线程,后台线程又称守护线程。我们常说的主线程就是前台线程。
前台线程的结束决定进程是否结束。不管后台线程如何,前台线程全部结束,进程就结束。
创建出来的线程默认也是前台线程,不过通过设置 thread.setDaemon(true) 可以将前台线程转变为后台线程。
代码示例:
public class demo1 { public static void main(String[] args) { Thread thread=new Thread(()->{ while(true){ for(int i=0;i<10;i++){ System.out.println("新线程"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread.setDaemon(true); //不设置thread.setDaemon(true),主线程运行结束时,新线程thread一直在运行 //设置thread.setDaemon(true)后,主线程运行结束,进程直接结束 //注意:必须 要在创建线程之前 就将thread设置为后台线程 thread.start(); System.out.println(thread.isDaemon()); } }
运行截图:
- 线程是否存活
- 线程是否被中断
获取到线程当前是否被中断
二、 线程的方法
1. 线程的构造方法
方法1&2就是我们创建线程时所用的两种构造方法,方法3&4创建线程的原理与1&2一样.
方法3使用示例:
class MyThread extends Thread {
String s;
public MyThread(String str) {
this.s=str;
}
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println(s + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class demo1 {
public static void main(String[] args) {
MyThread thread=new MyThread("a线程");
thread.start();
}
}
方法4使用示例:
class MyRunnable implements Runnable {
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println("新线程" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class demo1 {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
Thread thread=new Thread(runnable,"a线程");
thread.start();
}
}
区别:方法1、2创建的线程用的是默认名字;使用方法3、4,可以自己给线程取个名字
再次使用 jconsole 工具
2. 获取线程对象
使用 Thread.currentThread() 方法,返回当前线程对象
3. sleep方法
线程运行的速度是非常快的,我们有时候观察线程打印就需要借助sleep让线程的打印慢下来。
Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。
🔊 sleep是怎么起作用的呢?
每个线程有自己的PCB,操作系统将多个PCB组合成双向链表进行管理。另外,操作系统中又多个链表,不同状态的PCB在不同的链表上。比如多个处于就绪状态的线程集中在一个链表上,我们把这些线程的组合叫做就绪队列。
start后,没有sleep前,线程是在就绪队列上的。
使用sleep,线程从原来的就绪队列调度回阻塞队列
4. 中断线程
🙋🏻♂️为什么需要中断线程?
线程一旦进到工作状态,就会按照任务步骤进行工作,不完成是不会结束的。但有时系统出现一些问题,需要停止线程工作,就需要中断线程。
🙋🏻♀️ 怎么中断线程?
中断线程的方式有两种,下面我们分别来看这两种方法的具体操作...
① 自己设置标志位控制线程中断
public class demo1 {
public static boolean isQuit=false;
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!isQuit){
System.out.println("新线程运行中...");
try {
Thread.sleep(1000); //使用sleep,让线程执行任务的速度慢下来便于观察
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束");
});
thread.start();
//Thread.sleep(1000);//使用sleep,可以阻塞主线程。间接影响到新线程任务执行的次数
isQuit = true;
}
}
运行结果:
分析运行结果:
由于主线程main与新建线程thread并发运行,当主线程运行到 isQuit = true 位置(该代码作用是改变新线程的while循环条件)时,新线程运行到哪里了不确定。
此时新线程运行的位置,简单来说有两种情况:
① 新线程运行到while循环条件前,执行到while循环条件代码处退出
- 一次任务都没执行
- 已经执行过几次任务
② 新线程运行到while循环条件后,下一次执行到while循环条件处退出
- 至少会执行一次任务
一句话总结:不论新线程执行在while循环前还是while循环后,都会影响到while循环条件从而影响代码执行。在这几种情况下,随着新线程执行任务次数的不同,运行结果不同。
② 利用系统自带的标志位控制线程中断
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("新线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//===== 默认异常处理 ======
//打印异常
e.printStackTrace();
//===== 自己写异常处理 ======
//[1] 直接退出
// break;
//[2] 稍后退出, 此处的 sleep 可以换成任意的用来收尾工作的代码
// try {
// Thread.sleep(3000);
// } catch (InterruptedException ex) {
// ex.printStackTrace();
// }
// break;
//[3] 不退出,什么都不干就行
}
}
System.out.println("新线程结束");
});
thread.start();
Thread.sleep(1000);
System.out.println("控制新线程结束");
thread.interrupt();
}
}
运行结果:
注意:
自己设置的标志位,改变变量isQuit的值后,不论新线程当前执行位置在哪里,最终都会表现为“循环条件改变而导致程序退出”。但是程序自带的标志位则不然...
分析运行结果:
调用程序自带的标志位,新线程执行到while前和while后,会有两种表现方式:
( 在这里将while前的线程的状态称为非阻塞状态,while后线程的状态称为阻塞状态 )
① 通过改变标志位处理中断
当新线程处于非阻塞状态(未运行到Thread.sleep),主线程与此同时运行的interrupt() 会导致循环条件改变而直接退出循环;
② 通过Thread.sleep的异常处理机制处理中断
当线程处于阻塞状态(运行到Thread.sleep),说明已经执行完了while循环条件那一段 代码,那么主线程与此同时运行的interrupt()不会影响到while循环条件。但是程序可以通 过对Thread.sleep()捕捉到的 interrupt 中断异常进行对应的处理,实现程序的中断。
一句话总结:线程处于非阻塞状态,影响循环条件;线程处于非阻塞状态,Thread.sleep会处理中断异常,通过自定义异常处理,实现不同的中断线程方式。
5. join控制多线程顺序
🙋🏻♂️ 为什么使用join ?
由于线程调度的随机性,多线程的执行顺序十分随机。有些情况,我们需要线程按照我们想要的执行顺序去执行。就需要用到join方法
🙋🏻♀️ 怎么使用join ?
使用join的格式:线程对象.join
join的作用: 阻塞 调用了"线程对象.join" 的线程
代码示例1:谁来调用join?谁调用join有效果?
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
for(int i=0;i<10;i++){
System.out.println("a线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2=new Thread(()->{
for(int i=0;i<10;i++){
System.out.println("b线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//测试: ??? 谁来调用join有效果
// //第一组
// thread1.start();
// thread2.join();
// thread2.start();
// //第二组
// thread1.start();
// thread1.join();
// thread2.start();
// //第三组
// thread1.start();
// thread2.start();
//测试发现: 第一组第三组结果相同,与第二组不同
//结论:只有使用已创建的线程对象去调用join,才可以起作用
//分析运行结果:在第二组中,thread1.join是怎么起作用的呢?
// 主线程调用thread1.join,阻塞主线程。当线程1运行结束,阻塞接触。主线程继续运行调用thread2.start
// 线程2和主线程开始交替运行
while(true){
for(int i=0;i<10;i++){
System.out.println("主线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程.join,这个线程需要已经创建,否则没有意义。
我们后续提到的线程.join,都是已经创建出来的线程
代码示例2:正确调用join后的运行结果分析
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
for(int i=0;i<3;i++){
System.out.println("a线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread1.join();//在主线程中调用join,阻塞主线程
while(true){
for(int i=0;i<3;i++){
System.out.println("主线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
现有线程thread1、主线程两个线程,运行过程:
主线程与thread1线程被随机调度,开始并发运行
->
主线程中调用thread1.join,主线程阻塞;此时thread1不确定运行是否结束,有两种情况:
① thread1没有运行完,join方法等待thread1结束后接触阻塞
②thread1已经运行结束,join方法不再等待直接返回
->
主线程阻塞解除后,继续运行
->
进程结束
关键看被等待的线程thread1运行是否结束
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
for(int i=0;i<10;i++){
System.out.println("a线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2=new Thread(()->{
for(int i=0;i<10;i++){
System.out.println("b线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//测试二: ??? 调用顺序是否会影响阻塞状态
//第一组
// thread1.start();
// thread2.start();
// thread1.join();
// thread2.join();
//第二组
// thread1.start();
// thread2.start();
// thread2.join();
// thread1.join();
// //第三组
// thread1.start();
// thread1.join();
// thread2.start();
// thread2.join();
//测试发现: 第一组第二组结果相同,与第三组结果不同
//结论:当两个join在一起被调用时,此时join的顺序不会影响阻塞最终结果
while(true){
for(int i=0;i<10;i++){
System.out.println("主线程"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
第三组测试前面两个线程的运行分析是一样的,现在来分析一下第二组和第三组。
现有线程thread1、thread2、主线程三个线程,运行过程:
thread1(t1)、thread2(t2)、主线程被随机调度,开始并发运行
->
t1.join - t2.join 还是 t2.join - t1.join 关键看哪个线程先结束
① 如果t1先结束t2后结束
当t1结束的时候,主线程中的t1.join已经解除阻塞,继续执行t2.join又被阻塞。然后t2结 束,主线程的t2.join也结束阻塞。主线程继续执行
② 如果t2先结束t1后结束
当t2结束的时候,由于t1没结束,主线程执行到t1.join被阻塞。当t1结束之后,t1.join解除阻塞,主线程继续执行到t2.join。由于t2已经结束了,t2.join不用再等待t2于是也就不阻塞主线程直接返回。主线程继续执行。
->
进程结束
这里的关键在于被等待的线程t1、t2谁先结束。
另外,其实不管谁先结束,打印出来的运行结果都差不多,都是t1、t2两个线程交替打印。而不是某一个线程运行结束,其他线程才开始打印。
🔊 join()总结
【 join 的行为】
阻塞调用 '线程.join' 的线程
1. 如果被等待的线程还没执行完,就阻塞等待
2. 如果被等待的线程已经执行完了,直接就返回
拓展:阅读资料时,看到过一句话:“join是等待当前的线程执行结束”。
我个人认为这句话不是在所有场合都适用。比如当线程1内部调用线程1.join,会导致线程1自己阻塞自己,和这句话放在一起有点不好理解。所以私以为 join 的作用说成是“阻塞调用它的线程”更为合适...