新内容开始之前,我们总结一个知识点.
Thread类中的start方法和run方法的区别?
start():
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行, 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。 一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止.
run():
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
目录
1. Thread 类的方法
2. 线程中断
2.1 设置一个结束标志位
2.2 interrupt() (Thread 中自带的标志位)
2.3 为什sleep要清空标志位?
3.线程等待
1. Thread 类的方法
2. 线程中断
线程的中断,就是将一个线程停下来.
线程的终止:本质上只有一种,就是让该线程的入口方法执行完毕.
2.1 设置一个结束标志位
/**
* Created with IntelliJ IDEA.
* Description:线程中断1-自己创建变量,控制循环
* User: YAO
* Date: 2023-05-08
* Time: 16:28
*/
public class ThreadDemo7 {
public static boolean isQuit = false;
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (!isQuit){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("新线程结束");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
}
}
通过主线程进行修改标志位,使得新线程中的代码停止了循环.
思考:此时如果将isQuit,不写成成员变量,写在Main方法之中,还会达到想要的效果吗?
答案:不可以 虽然lambda表达式能够访问局部变量,但是lambda表达式有变量捕获,捕获的变量必须是没有进行修改过的变量,main方法中后面对isQuit做了修改,所以不可以写在main方法中.这个变量要么是被final修饰,如果不是被final修饰的 你要保证在使用之前,没有修改.
2.2 interrupt() (Thread 中自带的标志位)
public class ThreadDemo8 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
//currentThread() 获取当前线程的实例
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();//打印当前异常位置的调用栈
}
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将标志位设置为true
thread.interrupt();
}
}
简要介绍一下上面的代码.
首先认识两个方法:
1. Thread.currrentThread() :是获取当前线程的实例.
2. Thread.currrentThread() .isInterrupted() --->默认是false
interrupt方法的作用: * 1.设置标志位为true * 2.如果该线程处于堵塞的状态(比如正在执行sleep),此时就会把阻塞状态给唤醒.通过抛出异常的方式让sleep立即结束.
当我们运行上述代码的时候,我们会看见下述问题
出现的原因:
主线程中通过设置标志位interrupt,就立刻将sleep给唤醒,当sleep被唤醒的时候,sleep会自动的把isInterrupted标志位给清空 (true->flase),这样新线程的循环就被打通了,继续的执行sleep下一次遇见的时候就标志位就是false,那么就执行sleep本身,也不抛出异常,循环一直走下去,如果想要该循环截止,就在抛出异常的时候加一个break.
多线程的代码执行顺序不是“从上到下”,而是每个线程独立执行的。
2.3 为什sleep要清空标志位?
清空标志位的目的是让线程本身能够对于线程何时结束,有一个更明确的控制.
当前的interrupt方法效果,并不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,立即结束还是等会结束,都是代码来灵活控制的。
interrupt只是建议,不是命令。为什么java不强制设定为“命令结束”的操作呢?只要调用interru就立即结束呢?因为设定成这种,非常不友好。线程t何时结束,一定是t自己最清楚。交给t自己决定,就比较好。
3.线程等待
使用thread.join()
线程之间是并发执行的。操作系统对于线程的调度是无序的,无法判定两个线程谁先执行结束,谁后执行结束。
package threading;
public class ThreadDemo8 {
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println("hello t");
});
t.start();
System.out.println("hello main");
}
}
上述代码中,先输出hello main 还是先输出hello t是无法确定的,大概率先输出hello main.必定创建线程是需要开销的,但是我们海狮无法确定优先顺序,这就引出一个问题,当我们需要一个场景就是,必须等待线程2执行完了再执行线程1,这时候我们怎么办呢?
使用关键字 join
public class ThreadDemo9 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println("hello t");
});
thread.start();
System.out.println("hello main1");
//在main线程中调用thread.join,main线程等待Thread线程,等Thread执行完了之后,在继续执行main线程.
thread.join();
System.out.println("hello main2");
}
}
上述代码的意思是,在主线程中加了一个t.join,就是等待t线程结束之后,再执行t.join之后的main线程中的代码.也就是在t线程结束之前,main线程处于一个堵塞的状态,暂时不参加CPU的调度执行.运行结果如下:
补充:
join还有一个版本,可以填写一个参数,作为“超时时间”,等待的最大时间。
join的无参数版本,效果就是“死等”,不见不散。
join的有参数版本,则是执行最大超时时间,如果等待的时间到了上限,还没有等到,咱就不等了。
那么此时就会联想到一个问题,会不会出现两个线程互相等待的情况.
答案:是会出现的 那种情况称为死锁,是bug。 比如家钥匙锁车里了,车钥匙锁家里了。