文章目录
- 一. Thread提供的属性和方法
- 1. Thread常见的构造方法
- 2. 属性及获取方法
- 二. 中断(终止)一个线程
- 1) 自己来实现控制线程结束的例子
- 2) 使用Thread提供的interrupt和isInterrupted方法来实现控制线程结束
一. Thread提供的属性和方法
1. Thread常见的构造方法
③ 在创建线程时, 可以给线程起名字, 但是是否取名字, 对于线程本身的运行效果是没有影响的
但是起名字有个好处, java进程运行过程中, 可以通过工具看到每个不同进程的名字, 出现问题时, 更直观的把问题线程和代码关联起来(jconsole / IDEA 调试器, 都能看到线程的名字, 如果没有命名, 也有默认的名字Thread-0, Thread-1…)
⑤这个构造方法是把多个线程进行分组, 分组之后, 就可以针对不同的组批量进行控制
但是在开发中很少使用,这种写法目前实际开发中更多的是被线程池取代了
2. 属性及获取方法
① ID
这里的ID和pcb中的pid是不同的, 是jvm自己搞得一套id体系 (java代码无法获取到pcb中的pid)
④ 优先级
虽然java提供了优先级接口, 实际上就算修改了优先级, 现象也不明显, 因为你修改了优先级是一回事, 系统调度又是另外一回事, 这里修改优先级只能是一个"建议参考", 具体还是人家系统以自身为标准
⑤是否是后台线程
前台线程: 这样的线程如果不结束运行, 此时java进程是一定不会结束的
后台进程: 这样的线程, 即使继续再执行, 也不能阻止java进程结束
前台线程可以有很多个, 多个前台线程, 必须等到最后一个前台线程结束, 整个进程才能结束
在java代码中, main线程, 就是前台线程
程序猿创建出来的线程, 默认情况下都是前台线程
在jcondole中看到的jvm中包含的一些其他的内置线程, 属于后台线程
可以通过setDaemon方法把线程设置为后台线程:
上述代码, 运行结果为:
只有当我们写的线程运行完成后进程才会结束, 而main进程执行完start后直接结束了, 对进程没有影响
接下来我们将自定义线程设置成后台进程:
在start之前加入上述语句, 运行结果为:
此时, 进程中, 只有main一个前台进程了, 只要main结束了, 整个进程就结束了, main执行完start后立刻就结束了, 此时t还没来得及打印呢, 进程就结束了, 里面的线程自然也就结束了
但是注意:
此处也有一定的概率, 出现thread打印一次, 然后进程结束的情况, 因为线程是抢占式执行的, 调度顺序是不确定的, 就要看main先结束执行还是t先打印
注意: 关于线程的各种属性的设置, 一定要在start前面, 一旦线程已经启动, 再设置就来不及了
⑥是否存活
指的是系统中的线程PCB是否存在
注意: Thread对象的生命周期和PCB的生命周期是不一定完全一样的
这个操作, 实在创建Thread实例, Thread对象已经产生, 但是此时内核中的PCB还没有诞生
这个操作, 才是真正在系统中创建出线程(PCB才真正创建出来并且加入到链表中)
举例1:
由于thread线程中没有内容, 所以t瞬间就执行结束了, 内核中的线程和pcb就被销毁了
但是在sleep结束之前, thread引用指向的对象, 仍然是存在的, 并没有被GC回收掉
所以此时我们通过isAlive判断一下发现是false:
举例2:
线程还没有执行完成, 此时判断线程是否存活:
二. 中断(终止)一个线程
终止线程, 在java中, 都只是"提醒,建议", 真正要不要终止, 还得线程本身决定
假设t线程正在执行, 其他线程, 只能提醒一下t是否要终止了, t收到这样的消息后, 也还是要自己决定
1) 自己来实现控制线程结束的例子
核心思路, 就是让需要终止的线程的入口方法尽快执行结束(跳出循环, 还是尽快return都可以)
我们创建一个boolean类型的成员变量, isRunning, 设置为true, 用来作为循环的条件, 控制进程的结束
等待三秒后, 让isRunning设为false, 此时循环就会结束, 进程结束
public class threadDemo7 {
private static boolean isRunning = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(isRunning){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread线程已经结束");
});
thread.start();
Thread.sleep(3000);
System.out.println("控制thread线程结束");
isRunning = false;
}
}
这样就实现了在其他线程中控制thread线程的效果
思考:如果我们把变量设置在main方法中, 是否可行?
发现isRunning标红了
这里的知识点是变量捕获
在Java中,变量捕获(Variable Capturing)通常指的是在匿名内部类或Lambda表达式中,引用外部作用域的局部变量。当在一个匿名内部类或Lambda表达式中引用外部作用域的局部变量时,该局部变量会被捕获并保存下来,以便在内部类或Lambda表达式中访问和使用。
但是捕获的变量必须要满足:
必须是final修饰或"事实"final,即没有被修改
在代码中, 我们后续修改了isRunning的值, 所以在lambda表达式中是无法捕获的
-
为什么写在类中作为成员变量, 就可以访问呢?
-
因为此时就不再是变量捕获了, 而是
内部类访问外部类的成员!
lambda表达式本质上就是匿名内部类, 实现了函数式接口
所以内部类访问外部类的变量, 本身就是允许的
但是现在又出现了新的问题, 如果thread线程中sleep10s, 那么我们在main中3s之后, 是无法让thread线程立刻终止的
2) 使用Thread提供的interrupt和isInterrupted方法来实现控制线程结束
isInterrupted方法:
是线程内置的标志位, 刚才我们是自己定义了一个boolean类型的变量, isInterrupted方法其实也是一个boolean变量
true表示线程要终止了
false表示线程要继续执行, 默认为false
currentThread方法是一个静态方法, 这个方法可以获取到当前线程, 即thread线程
因为在lambda表达式内部, 不能直接用thread线程, 因为还没有创建出来thread对象
interrupt方法:
这个方法, 就相当于是设置boolean值为ture
与自定义变量不同的是, 如果代码中有sleep, 这个方法, 可以让sleep等阻塞方法抛出一个InterruptedException异常, 从而达到唤醒sleep等阻塞方法的目的
上述代码, 三秒之后, 修改标志位, 并抛出InterruptedException异常, 捕获异常后, 执行catch块中的语句, 继续刨除RuntimeException异常, 线程终止
但是如果我们将catch 块中的语句修改一下, 只打印异常信息
此时运行就会发现:
修改标志位, 唤醒sleep, 运行catch块中的代码, 此时循环条件已经不再成立, 为什么还会继续打印hello thread呢?
其实, 是sleep在搞鬼
如果代码没有sleep, 确实是直接修改了标志位就完事了, 但是如果有sleep, 并且触发Interrupt的时候, 线程正在sleep, sleep被唤醒的同时, 会清除刚才的标志位(又改回false), 所以循环就继续进行了
那么之所以要将标志位改回来, 就是把是否结束进程的控制权交给程序猿自己, 也就是交给这个线程本身, 别的线程想让你终止, 但是控制权还在你自己的手上:
- 如果你不想结束
那么就可以在catch块中不做任何操作, 此时sleep清除标志位, 代码还会继续运行 - 如果想立即结束, 那么就可以直接break
- 如果想等会再结束, 那么就可以在catch中写写东西, 再break;