目录
- Day4:多线程(2)
-
- [1. catch语句](#1. catch语句)
- [2. sleep的处理](#2. sleep的处理)
- [3. Thread](#3. Thread)
-
- [3.1 Thread构造方法](#3.1 Thread构造方法)
- [3.2 Thread的属性](#3.2 Thread的属性)
-
- [3.2.1 ID](#3.2.1 ID)
- [3.2.2 优先级](#3.2.2 优先级)
- [3.2.3 后台线程](#3.2.3 后台线程)
- [3.2.4 存活](#3.2.4 存活)
- [3.2.5 start](#3.2.5 start)
- [3.2.6 中断](#3.2.6 中断)
-
- [3.2.6.1 控制线程结束代码](#3.2.6.1 控制线程结束代码)
- [3.2.6.2 interrupt和isInterrupted](#3.2.6.2 interrupt和isInterrupted)
Day4:多线程(2)
1. catch语句
catch语句中有两种代码
throw new RuntimeException(e);
:继续再抛出新的异常e.printStackTrace();
:打印异常调用栈
第一个是新版idea自动生成的,第二个是老版idea自动生成的,当前阶段使用哪个写法都可以,没有太大区别
但是catch中应该写什么,在实际开发中,是应该自定义的,都是规划好的,如下:
1)可能会打印一些日志,把出现异常的详情都记录到日志文件中
2)触发一些重试类的操作
3)触发一些"回滚"类的操作
4)触发一些报警机制(而是给程序员发短信/微信/打电话 告诉程序员你程序出问题了)
java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
2. sleep的处理
在main方法中处理sleep,可以throws ,也可以try catch ,但是在线程的run中就只有一个选择了,只能try catch
- throws也是方法签名的一部分,方法签名 包含了++方法名字、方法的参数列表、声明抛出的异常++,不包含返回值、public/private,在方法重写的时候,要求方法签名是一样的,然而父类的run方法并没有抛出异常,所以重写的时候,就无法抛出异常了
3. Thread
3.1 Thread构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用Runnnable对象创建线程对象,并命名 |
Thread(ThreadGroup group, Runnable target)【了解】 | 线程可以被用来分组管理 |
Thread(String name)
:可以在创建线程的时候,给线程起个名字,是否起名字,对于线程本身的运行效果,是没有任何影响的,但是起名字有个好处,Java进程运行过程中,可以通过工具(jconsole/IDEA)看到每个不同线程的名字,出现问题的时候,更直观的把问题的线程和代码关联起来(方便调试)Thread(ThreadGroup group, Runnable target)
:开发中很少用到,有的时候,希望把多个线程进行分组,分组之后,就可以针对不同的组批量进行控制,这种写法目前实际开发中更多的是被线程池取代了
java
package thread;
public class Dmeo6 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"自定义线程");
t.start();
//至此main方法结束,意味着main线程就结束了(销毁了)
}
}
这里没有看到main线程,一个进程启动,肯定得先有main线程调用main方法。注意,此处不是main线程没有被创建,而是执行太快,执行完毕了!main线程是JVM通过C++代码创建出来的,没有通过Java中的Thread类创建,就不会重写run
3.2 Thread的属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
3.2.1 ID
这里的ID和系统中PCB上的ID是不同的,是JVM自己搞得一套ID体系,Java代码无法获取到PCB的ID,虽然是一一对应的,但是编号不是一个体系
3.2.2 优先级
虽然Java提供了优先级接口,实际上就算修改了优先级,现象也不明显,自己修改了优先级是一回事,系统调度又是另一回事,这里的优先级只能是一个建议参考,具体还是要以系统自身为准,通过C调用系统原生API修改某个PCB里的优先级也是没有明显现象的
3.2.3 后台线程
- 前台线程指的是,这样的线程如果不运行结束 的话,Java进程是一定不会结束 的;后台进程指的是,这样的线程,即使继续在执行 ,也不能阻止Java进程结束,前台线程可以有多个,直到最后一个前台线程结束,进程才会结束
- 在Java代码中,main线程就是前台线程,程序员创建出来的线程,默认情况下都是前台线程 ,可以通过setDaemon方法来把线程设置为后台线程,在jconsole中看到的JVM中包含一些其他的内置的线程,就属于后台线程了,比如有的线程负责进行gc(垃圾回收),gc是要有周期性持续性执行的,不可能主动结束,要是把他设为前台,进程就永远都结束不了了
java
package thread;
public class Demo7 {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//设置为后台线程,必须设置在start之前,开弓没有回头箭
t.setDaemon(true);
t.start();
//此时进程中只有main是前台线程了,只要main结束,整个进程就结束了,main执行完start立即结束了,此时t还没来得及打印,进程结束了,里面的线程自然随之结束
}
}
注意:此处也是有一定概率出现t打印一次,然后结束进程的情况,这个事情就看是main先执行结束,还是t先执行一次打印(线程之间是抢占式执行,调度顺序不确定)
3.2.4 存活
指的是系统中的线程(PCB)是否还存在,Thread对象的生命周期和PCB的生命周期是不一定完全一样的
java
package thread;
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
Thread.sleep(1000);
System.out.println(t.isAlive());//true
}
}
上述写法会导致县城还没有执行完毕,但是t指向的对象就要被GC回收了
3.2.5 start
start真正创建线程(在内核中创建PCB)
- 一个线程需要先通过run/lambda把线程要完成的任务,定义出来,start才是真正创建线程,并开始执行
- 核心就是是否真的创建线程出来,每个线程都是独立调度执行的,相当于整个程序中多了一个执行流
- 一个Thread对象,只能start一次,要想再搞另一个线程,就需要创建另一个Thread对象
java
package thread;
public class Demo9 {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("hello t");
});
Thread t2 = new Thread(()->{
System.out.println("hello t2");
});
t.start();
t2.start();
}
}
3.2.6 中断
终止线程,在Java中,都只是"提倡、建议",真正要不要终止,还得线程本体来进行决定
t线程正在执行,其他线程,只能提醒一下t是不是要终止了,t收到这样的提醒之后,也还是得自己决定的
系统原生的线程中,其实有办法让别的线程被强制终止的,这种设定,其实不太好,所以Java没有采纳过来
线程之间调度是随机的,万一线程正在做一个很重要的工作,干了一半,强制结束可能引起一些bug
3.2.6.1 控制线程结束代码
核心思路:让需要终止的线程的入口方法尽快执行结束(跳出循环,还是尽快return都无所谓)
java
package thread;
public class Demo10 {
private static boolean isRunning = true;
public static void main(String[] args) {
Thread t = new Thread(()->{
// boolean isRunning = true;
while (isRunning){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t线程结束了");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//3s后,主线程修改isRunning的值,从而通知t结束
System.out.println("控制t线程结束");
isRunning = false;
}
}
如果是// boolean isRunning = true;
会出现编译出错
变量捕获:作为lambda或者匿名内部类,都能捕获到外面一层作用域中的变量名,就可以使用,变量捕获有一个前置条件,就是要求得是final或者事实final,即没有人修改其变量的值
然而使用private static boolean isRunning = true;
:原理是内部类访问外部类的成员,lambda本质上是一个匿名内部类,实现了函数式接口
3.2.6.2 interrupt和isInterrupted
java
package thread;
public class Demo11 {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(10000);//10s,但是会唤醒sleep抛出异常
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt();
}
}
Thread.currentThread().isInterrupted()
currentThread()
:是一个静态方法,这个方法能够获取到当前线程,获取到t这个引用isInterrupted()
:线程内置的标志位,boolean
变量,true表示线程要终止了,false表示线程要继续执行
t.interrupt();
- 通过这个方法,就相当于是设置
boolean
值为true - 除了能设置boolean值,还可以唤醒sleep等阻塞方法 ,即使正在sleep(10s),刚休眠1s
- 第一种写法,必须等待9s,才能让线程结束(sleep结束了,才能继续进行循环判定)
- 第二种写法,则立即就会让sleep抛出一个InterruptedException异常,不会再等待,立即就唤醒了
当使用Interrupt方法之后,此时要不要结束,都是t线程自己决定的
当进行了
t.interrupt();
之后,修改了标志位,但是由于sleep的存在,如果代码没有sleep,确实是直接修改了标志位就结束了,但是有sleep的时候,触发Interrupt的时候,线程正在sleep,sleep被唤醒(进入catch中)的同时,就会清除刚才的标志位(又改回false),之所以这样做是为了要让程序员自己决定要不要结束,是继续执行,还是要立即结束,还是要等会结束
java
//1.程序员认为继续执行
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}
//2.立即结束
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
break;
}
}
//3.等会结束
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//写一些逻辑之后,再break
break;
}
}