start
和 run
的区别:(经典面试题)
run
描述了线程要执行的任务,也可以称为“线程的入口”public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("hello t");
Thread t2 = new Thread(() -> {
System.out.println("hello t2");
});
t2.start(); //hello t2
});
t.start(); //hello t
}注意:start
会根据不同的系统,分别调用不同的 API
,来执行系统函数,在系统内核中,创建线程(创建 PCB
,加入到链表中)run
start
的执行速度一般是很快的(创建线程,比较轻量),一旦 start
执行完毕,新线程就会开始执行,调用 start
的线程也会继续执行(main
线程)(开始兵分两路,并发执行)main
创建的线程,因为 main
是程序执行入口hello t
线程的状态:由于
Java
中希望,一个Thread
对象,只能对应到一个系统中的线程,因此就会在start
中,根据线程状态做出判定。 如果Thread
对象是还没有调用start
的,此时就是一个NEW
状态,之后就可以顺利地调用start
如果Thread
已经调用过start
,就会进入其他状态, 只要不是NEW
状态,接下来执行start
都会抛出异常
线程的终止 B 正在执行,A 想让 B 结束
其实核心就是 A 要想办法,让 B 的
run
方法执行完毕,此时 B 就自然结束了而不是说 B 的
run
执行一半,A 直接把 B 强制结束了
public class Demo3 {
public static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
boolean isQuit = false;
Thread t = new Thread(() -> {
while(!isQuit){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程执行结束");
});
t.start();
Thread.sleep(2000);
//修改 isQuit 变量,就能狗影响到 t 线程的结束了
System.out.println("main 线程尝试终止 t 线程");
isQuit = true;
}
}
通过改变变量,让
t
线程跳出了while
循环,最终线程终止变量捕获是
lambda
表达式 / 匿名内部类的一个语法规则isQuit
和lambda
定义在一个作用域中,此时lambda
内部是可以访问到lambda
外部(和lambda
同一个作用)中的变量 但是Java
的变量捕获有特殊要求,要求捕获的变量得是final
/事实 final
(虽然没有final
修饰,但没有修改)Demo3 { //public static boolean isQuit = false; public static void main(String[] args) throws InterruptedException { boolean isQuit = false; Thread t = new Thread(() -> { while(!isQuit){ System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("t 线程执行结束"); }); t.start(); Thread.sleep(2000); //修改 isQuit 变量,就能影响到 t 线程的结束了 System.out.println("main 线程尝试终止 t 线程"); isQuit = true; //会报错 } }>将 `isQuit` 定义在 `main` 内部后image.png|555由于
isQuit
变量前后由false
被改为true
了,所以会报错若将最后一行注释掉,此时
isQuit
就是一个事实final
(虽然没有final
修饰,但没更改) ,isQuit
变量就会被捕获到,程序就不会报错undefined
上面写成成员变量,就可以正常修改访问,因为走的语法是“内部类访问外部类的成员”,和“变量捕获”无关
lambda
表达式本质上是一个“函数式接口”产生的“匿名内部类”,外面写的 “class Demo3
“ 就是外部类
Thread
类里面有一个成员,boolean
类型的 interrupted
false
,未被终止,但一旦外面的其他线程,调用一个 interrupt()
方法,就会设置上述标志位public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
//先获取到线程的引用
Thread currentThread = Thread.currentThread();
while(!currentThread.isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Thread.sleep(1000);
//在主线程中,控制 t 线程被终止,设置上述标志位
//将其由 false 设为 true,意味着循环就结束了,线程也就结束了
t.interrupt();
}
}
currentThread()
是Thread
类的静态方法,可以获取到调用这个方法的线程的实例,哪个线程调用,返回的引用就只想哪个线程的实例,类似this
lambda 的代码在编译器眼里,出现在
Thread t
上方,此时t
还没有被定义,所以不能直接用t
调用isInterrupted()
方法undefined
由于判定
isInterrupted()
和执行打印,这两个操作太快了,因此整个循环,主要的时间都是花在sleep
上
main
调用Interrupt
的时候,大概率t
线程正处于sleep
状态中,此处Interrupt
不仅仅能设置标志位,还能把刚才这个sleep
操作给唤醒比如,当还在
sleep
的时候,此时Interrupt
被调用了,此时sleep
就会被直接唤醒,并且抛出InterruptedException
异常由于
catch
中的默认代再次抛出异常,但没人catch
,最终就到了JVM
这一层,进程就直接异常终止了image.png
综上: interrupt
不仅能设置标志位,还能唤醒 sleep
catch
中生成的默认代码影响了执行的结果,导致我们看起来像整个进程都结束了catch
中的 throw
给干掉,换成一个打印“执行到 catch
操作”public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
//现货区到线程的引用
Thread currentThread = Thread.currentThread();
while(!currentThread.isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//throw new RuntimeException(e); //屏蔽掉生成的默认代码干扰
System.out.println("执行到catch操作");
}
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
}
}interrupt
把 sleep
唤醒了,触发异常,被 catch
住了。但是虽然 catch
住了,但是循环还在执行,看起来就像标志位没被设置一样首先,
Interrupt
肯定会设置这个标志位的其次,当
sleep
等阻塞的函数被唤醒后,就会清空刚才设置的Interrupted
标志位,下一轮循环判定的时候,就会认为标志位没有被设置,于是循环就会继续执行因此,如果确实想要结束循环,结束线程,就需要在
catch
中加上return
/break
清除标志位
清除标志位这个操作可以让编写 B 线程的程序员有更大的操作空间
A 希望 B 线程终止,B 收到这样的请求之后,B 需要自行决定,是否要终止或立即执行还是稍后执行 ~~~ (B 线程内部的代码决定,与其他线程无关)
- 如果 B 想**无视 A**,就直接 `catch`,啥都不做,B 线程仍然会继续执行。`sleep` 清除标志位,就可以使 B 做出这种选择,如果 sleep 不清除标志位的话,B 就势必会结束,无法写出让线程急速执行代码了
- 如果 B 线程想**立即结束**,就直接在 `catch` 中写上 `return` / `break`,此时 B 线程就会立即结束
- 如果 B 线程想要**稍后再结束**,就可以在 `catch` 中写上一些其他的逻辑(比如释放资源、清理一些数据、提交一些结果...... 收尾工作)。这些逻辑完成之后,再进行 `return` / `break`
操作系统针对多个线程的执行,是一个“随机调度,抢占式执行的过程”。因为我们写代码的时候不希望是随机,希望是确定,所以期望通过一些变成手段来对这里的随机进行干预
A
和 B
A
线程中调用 B.join()
,意思就是让 A
线程等 B
线程先结束,然后 A
再继续执行B
是被等待的一方public class Demo5 {
public static void main(String[] args){
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("这是线程 t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程 t 结束");
});
t.start();
//让主线程等待 t 线程
System.out.println("main 线程开始等待");
try {
t.join(); //执行到这里,main线程阻塞等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//当 t 线程执行结束后,join才会返回,main才会继续执
System.out.println("main 线程等待结束");
}
}
//t 线程先结束,main 线程后结束
main
先等待,然后 t
执行了半天才结束,此时 main
在阻塞。undefinedpublic class Demo6 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
System.out.println("hello t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t1 结束");
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
System.out.println("hello t2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2 结束");
});
t1.start();
t2.start();
System.out.println("main 开始等待");
t1.join();
t2.join();
System.out.println("main 结束");
}
}t
先结束,然后 main
才开是 join
,这个时候也不会出现阻塞,因为 t
线程已经结束了,而 join
就是用来确保被等待的线程先结束,若已经结束了,join
就不必再等待了main
线程等待别人join
都是写在 main
里面的,所以都是 main
线程等待 t1
和 t2
,t1
和 t2
之间没有等待关系t2
中写 t1.join
,则 t2
需要等待 t1
线程先执行完想在某个线程中,获取到自身的 Thread 对象的引用,就可以通过 currentThread 来获取到
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread t = new Thread(()->{
//需要在 t 中调用主线程.join
System.out.println("t 线程开始等待");
try {
mainThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t 线程结束");
});
t.start();
Thread.sleep(1000);
System.out.println("main 线程结束");
}
}
//打印顺序:
//t 线程开始等待
//main 线程结束
//t 线程结束
任何线程中,都可以通过这样的操作,拿到线程的引用
任何需要的时候,都可以通过这个方法来获取到
Thread.sleep
可以让调用的线程阻塞等待,是有一定时间的sleep
,就会使这个线程不参与 CPU
调度,从而把 CPU
资源让出来给别人使用sleep
这种操作,称为“放权”,放弃使用 CPU
的权利CPU
占用率过高,就可以通过 sleep
来进行放权改善CPU
就是给程序用的,但是有的程序可能包含很多线程,这些线程之间是有“轻重缓急”的sleep
来更明显的影响到这里的 CPU
占用原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。