欢迎关注个人主页:逸狼
创造不易,可以点点赞吗~
如有错误,欢迎指出~
目录
线程的核心操作
创建线程start
线程的状态
线程的终止
定义一个变量实现
利用标志位实现
加上break/return 结束线程
线程等待
join 无参数版本
两个线程等待
多个线程等待
缺陷
join有参数版本
线程的核心操作
创建线程start
一个经典的面试题:start和run之间的区别
- start:调用系统函数,真正在系统内核中创建线程(创建PCB,加入到链表中),此处的start 会根据不同的系统,分别调用不同的api(windows,linux,mac...),创建好新的线程,再单独来执行run.
- run:描述了线程要执行的任务,也可以称为"线程的入口"
start的执行速度一般是比较快的(创建线程,比较轻量),一旦start执行完毕,新线程就会开始执行,调用start的线程,也会执行main. 调用start,不一定非得是main线程调用,任何线程都可以创建其他线程,如果系统资源充裕,就可以任意的创建线程(但线程不是越多越好)
start的本质是调用系统的api,系统的api会在系统内核里 ,创建线程(创建PCB,加入链表)
线程的状态
由于Java希望一个Thread对象 只能对应到一个系统中的线程,因此会在start中 根据线程的状态做出判定:
- 如果Thread对象 没有调用过start,此时状态是一个NEW状态,接下来就可以顺利调用start
- 如果已经调用了start,就会进入到其他状态,只要不是NEW状态,接下来start就会抛出异常
所以再次强调: 一个Thread对象,只能调用一次start,如果多次调用就会出问题(一个Thread对象,只能对应系统中的一个线程)
线程的终止
Java中让一个线程结束是一个更'温柔'的过程,比如B正在运行着,A想让B结束,其实核心是A想办法(如何将B的run能够更快的执行完)让B的run方法执行完毕,此时B自然结束了, 而不是B的run执行一半,A直接把B强制结束.
定义一个变量实现
public class Demo6 {
private static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
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(1000);
System.out.println("main线程尝试终止 t线程");
//修改isQuit变量,就能够影响到t线程结束
isQuit=true;
}
}
其中判断条件isQuit要写在main方法的外面 作为成员变量,内部类(lambda表达式本质上是一个'函数接口'产生的 匿名内部类)是可以访问外部类的成员
知识复习: 变量捕获
变量捕获是lambda表达式/匿名内部类 的一个语法规则
isQuit 和 lambda定义在一个作用域中,此时的lambda内部是可以访问到lambda外部 的,Java把同作用域中的所有变量都给捕获了,但是Java的变量捕获要求捕获的变量得是final/ 事实final(这是Java的特殊限制,c++,js等语言没有这个限制)
利用标志位实现
Interrupt方法能够设置标志位, 也能唤醒sleep等阻塞方法.
sleep被唤醒之后,又能清空标志位
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t =new Thread(()->{
Thread currentThread =Thread.currentThread();
while(!currentThread.isInterrupted()){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Thread.sleep(3000);
//在主线程中 控制t线程被终止,设置上述标志位
t.interrupt();
}
}
Thread类里面有一个成员变量 是interrupted (Boolean类型),初始情况下这个变量是false(未被终止),但是一旦外面的其他线程调用一个interrupt方法,就会设置上述标志位
修改代码,停止报错
加上break/return 结束线程
所以,Java中 终止线程是一个'温柔'的过程,不是强行终止 ,举例: 如果A希望B线程终止,B收到这样的请求后,B需要自行决定 是否要终止/立即 /稍后 终止(B线程内部的代码自行决定,其他线程无权干涉)
- 如果B线程想无视A,就直接catch中,啥都不做,B线程仍然会继续执行(sleep清除标志位,就可以使B能够做出这样的选择,否则B势必会结束)
- 如果B线程想立即结束,就直接在catch中加上return或者break.此时B线程会立即结束
- 如果B线程想稍后结束,就可以在catch中加上一些其他的逻辑(比如 释放资源,清理一些数据,提交一些结果...收尾工作) 这些逻辑之后,再进行return或者break.
线程等待
操作系统 针对多个线程的执行,是一个 '随机调度,抢占式执行'过程, 线程等待 就是在确定两个线程的'结束顺序'(无法确定两个线程调度执行的顺序,但可以控制 谁先结束,谁后结束), 本质上是让后结束的线程 等待先结束的线程即可(此时后结束的线程就会进入阻塞,一直到先结束的线程真的结束了,阻塞才解除)
join 无参数版本
对于join来说,无参数版本会持续等('死等'). 被等待的线程,只要不执行完,这里的等待就会持续阻塞
两个线程等待
使用join 比如,有两个线程a和b,在a中调用b.join,表示让a线程等待b线程先结束,然后a再继续执行(b是被等待的一方,先结束)
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
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线程开始等待");
t.join();
System.out.println("main线程等待结束");
}
}
如果上面的 t线程先结束了,main线程就不必等待了(join就不会发生阻塞),join 就是确保被等待的线程能够结束,如果已经结束了,join就不必在等了.
任何的线程之间都是可以相互等待的
下面是 t线程等待 主线程的代码示例
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
//t线程等待主线程
Thread mainThread =Thread.currentThread();//拿到主线程对象
Thread t =new Thread(()->{
System.out.println("t线程开始等待...");
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t线程等待结束");
});
t.start();
Thread.sleep(2000);
System.out.println("main线程执行结束.");
}
}
获取主线程对象 和 sleep的理解:
多个线程等待
相互等待的线程 也不一定是两个线程之间,一个线程也可以同时等待多个别的线程,或者若干个线程之间也能相互等待
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("线程t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1执行结束");
}) ;
Thread t2 =new Thread(()->{
for (int i = 0; i < 4; i++) {
System.out.println("线程t2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2执行结束");
});
t1.start();
t2.start();
System.out.println("main线程开始等待...");
t1.join();
t2.join();
System.out.println("main线程结束等待.");
}
}
上述代码中,主线程等待t1和t2线程,t2线程等待t1,所以结束顺序是t1, t2, main线程
t1, t2和主线程这三个日志的顺序是不确定的(不确定哪个先执行),但是由于t1和t2有创建开销,一般来说是主线程先执行,t1和t2 的顺序不确定.
缺陷
对上述 join无参数版本会出现'死等'的状态 一旦被等待线程代码 出现了一些bug,就可能使这个线程迟迟无法结束,从而使等待的线程一直无法执行其他操作.