【Thread类其他的属性和方法】
给线程命名的方法,不起名字则默认叫做Thread-0,Thread-1……
【线程的属性】
ID,名称,状态,优先级:JVM自动分配,不能手动设置
状态:java中把线程的状态,分为阻塞与就绪
优先级:设置不同的优先级,会影响到系统的调度,这里的影响是基于“统计”规则的影响,直接肉眼观察,很难观察到结果
【前台,后台线程】
如果这个线程在执行过程中,能够阻止进程结束此时这个线程就是“前台线程”
如果这个线程在执行过程中,不能阻止进程结束(虽然线程在执行着,但是进程要结束了,此时这个线程也会随之被带走),这样的线程就是“后台线程”
【前台,后台线程】
如果这个线程在执行过程中,能够阻止进程结束,此时这个线程就是“前台线程”
如果这个线程在执行过程中,不能阻止进程结束(虽然线程在执行着,但是进程要结束了,此时这个线程也会随之被带走),这样的线程就是“后台线程”
举个例子:
上述过程中,主持人决定了这场宴席什么时候结束,当他宣布结束时,宴席就散了,若“我”是个只会干饭的小透明,无法决定宴席何时结束,在主持人还没有宣布宴席结束时溜了也不影响宴席继续
那么“我”就是“后台线程”
1.进程要结束,(前台线程要结束),后台线程无力阻止
2.后台线程先结束了,也不影响进程的结束
主持人就是“前台线程”
1.前台线程宣布结束,此时进程就结束,后台线程也会随之结束
2.前台线程不结束,后台线程结束了也不影响
【守护线程】
public static void main(String[] args) {
Thread t = new Thread(() ->{
while(true){
System.out.println("hello B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//主线程不写循环,意味着主线程瞬间执行完了
}
主线程执行完了,但t还在执行,此时t默认是前台线程,只要它没有运行完,进程就不会停止
因此可以加上以下代码:t.setDaemon(true);
public static void main(String[] args) {
Thread t = new Thread(() ->{
while(true){
System.out.println("hello B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setDaemon(true);
t.start();
它可以把t设置为守护线程(后台线程),不再能阻止整个进程结束
【是否存活】
代码中创建的new Thread对象,生命周期和内核中的实际线程不一定一样,可能会出现Thread对象仍然存在,但内核中的线程不存在的情况
Thread t = new Thread(() ->{
for(int i = 0;i < 3;i++){
System.out.println("hello B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(t.isAlive());//为false代表不存在,true代表存在,第一次在start之前调,肯定false
t.start();
System.out.println(t.isAlive());//第二次在start之后调,是true,说明内核中已经有了这个线程
Thread.sleep(5000);
System.out.println(t.isAlive());//这边sleep完的时候,线程早就结束了,所以是false
但由于线程的调度顺序是不确定的,如果两个线程都是sleep3000,此时时间一到,两个线程谁先执行谁后执行就不确定了
【线程的核心操作】
【创建线程start】
谈一谈start和run之间的差别(重点掌握):
run:描述了线程要执行的任务,是“线程的入口”
start:调用系统api,系统的api会真正在系统内核中创建线程,创建好新的线程后再单独执行run
其执行速度较快(创建线程比较轻量),一旦start执行完毕,就会兵分两路,新线程开始执行,调用start的线程(主线程)也会继续执行
此外,调用start,不一定非得是main线程调用,任何的线程都可以创建其他线程,如果系统资源充裕,就可以任意的创建线程
但要注意,一个Thread对象只能创建一个线程
【线程的终止】
线程A和线程B
B正在运行,A想让B结束,核心就是A想办法让B的run方法执行完毕,此时B就自然结束了,而不是说B的run执行一半时A直接把B强制结束了
Java中结束线程是一个“温柔”的过程,主要是担心B某个工作执行了一半就强制结束了,此时B对应的结果数据就是一个"半成品"(更希望是一个完整的过程)
Thread t = new Thread(() ->{
while(!isQuit){
System.out.println("hello B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t线程执行结束");
});
t.start();
Thread.sleep(2000);
//修改isQuit变量,就能影响到t线程的结束了
System.out.println("main尝试终止t线程");
isQuit = true;
}
设置一个isQuit变量,就可以让线程提前结束
【通过获取线程的引用来终止线程】
Thread类中有一个成员,boolean类型的interrupted,初始情况下这个变量是false,一旦外面的线程调用了一个interrupted方法,就会设置上述标志位,变更为true
Thread t = new Thread(() ->{
Thread currentThread = Thread.currentThread();
while(!currentThread.isInterrupted()){
System.out.println("hello B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t线程执行结束");
});
t.start();
Thread.sleep(2000);
t.interrupt();
currentThread是Thread类的静态方法,可以获取到调用这个方法的线程的实例,哪个线程调用,返回的引用就指向哪个线程的实例
但此处会产生报错
由于判定interrupted和执行打印这两个操作太快了,因此整个循环都是把时间花在了sleep上,main调用interrupt时,大概率t线程还处于sleep状态,此处interrupt不但能设置标志位,还能唤醒刚才的sleep操作
比如,此时sleep刚睡了100ms,还剩余900ms,此时interrupt被调用了会直接唤醒sleep,并且抛出InterruptedException异常,由于catc中默认代码再次抛出异常,再次抛出的异常没被catch,最终到了JVM,进程就直接异常终止了
Thread t = new Thread(() ->{
Thread currentThread = Thread.currentThread();
while(!currentThread.isInterrupted()){
System.out.println("hello B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("执行到catch操作");
break;
}
}
System.out.println("t线程执行结束");
});
t.start();
Thread.sleep(3000);
t.interrupt();
sleep被唤醒后,会清空interrupted设置的标志位,导致无法结束循环,因此若确实想结束循环,结束线程,需要在catch中加上return/break
这样的设立方式是为了让线程有更多选择
1.若B线程想无视A,就直接catch中啥也不做,B线程中仍然会继续执行
sleep清除标志位就可以让B做出这样的选择,若不清除,B就会结束,无法让线程继续执行
2.若B想立即结束,就在catch中加上return/break,此时B会立即结束
3.若B想稍后再结束,就可以在catch中写上一些其他的逻辑,这些逻辑完成后,再进行return/break
这就给了程序员更多的操作空间
【总结】
1.interrupt方法能设置标志位,也能唤醒sleep等阻塞方法
2.sleep被唤醒后,能清空标志位