启动线程--start
Thread使用start方法启动一个线程;对于同一个Thread类,start只能调用一次
使命:立即在内核中创建一个新的线程,新线程和之前线程是“并发执行”
调用start创建新的线程:本质是start会调用系统api来完成线程操作
package thread;
public class ThreadDemo9 {
public static void main(String[] args) {
Thread t=new Thread(()-> {
while (true) {
System.out.println("hello thread");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
//第二次调用出现异常(非法的线程状态异常)
t.start();
}
}
此处的运行结果
第一次打印,第二次为非法的线程状态异常,并未因异常进程崩溃
区分start和run
引入经典面试题区分start和run
public static void main(String[] args) {
Thread t=new Thread();
//t.start(); 创建一个新的线程,由新的线程执行hello main线程继续执行后续循环
t.run();//在main主线程中打印hello 停留在run main的循环无法执行
}
while (true){
System.out.println("hello main");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
.start(); 创建一个新的线程,由新的线程执行hello main线程继续执行后续循环
t.run();在main主线程中打印hello 停留在run main的循环无法执行
运行结构都一样,但线程执行方式不同,体现多线程并发编程
终止线程
线程run(入口方法)执行完毕
线程提前终止核心:run方法提前结束
自定义变量作为标志位--isQuit
取决于代码的实现方式--在另一个线程何时修改isQuit的值
通过引入标志位,如下代码结束线程
package thread;
public class ThreadDemo5 {
private static boolean isQuit=false;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!isQuit) {
System.out.println("我是一个线程,工作中!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程工作完毕!");//原本是死循环
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程结束!!");
isQuit = true;
}
}
区分两种打印方式
System.out.println("线程结束!!");//主线程必须打印完毕才能设置isQuit,t线程才会结束--即打印顺序确定
isQuit = true;
isQuit = true;
System.out.println("线程结束!!");//设置isQuit后main和t打印不确定
运行结果如下
main线程想要t线程结束,t线程对代码有所支持,不是t里代码都能提前结束
run方法和main方法是两个线程,执行顺序不确定
内置变量--Thread 实例提供
currentThread 方法
可以用来获取当前线程实例(t);
哪个线程调用这个方法,得到的就是哪个线程的实例(类似 this)
isInterrupted 方法是用来查看当前线程是否被中断;如果一个线程被中断,那么得到的结果就为 true,它其实就相当于标志位
代码如下:
public static void main(String[] args)throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()){
System.out.println("我是一个线程,工作中!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程工作完毕!");
});
t.start();
Thread.sleep(1000);
//使用interrupt方法,来修改标志位的值
System.out.println("线程结束!!");
t.interrupt();//相当于isQuit=true 本质是使用Thread实例内部自带的标志位来代替isQuit
}
}
运行结果
分析代码出现异常(interrupt导致)但并未结束,若删掉匿名内部类中的 sleep,那么 interrupt 可以让线程顺利结束
即sleep 导致结果不同:
在执行 sleep 的过程中,调用 interrupt,可能会导致 sleep 的休眠时间还没到,就被提前唤醒了
被提前唤醒后,会做两件事:
①抛出 InterruptedException (这个异常紧接着就会被 catch 捕获到)
②清除 Thread 对象的 isInterrupted 标志位
通过 Interrupt 方法把标志位设为 true 了,但是 sleep 被提前唤醒后就把标志位设回 false,所以导致循环继续执行;想让线程结束,只需在 catch 中加上 break ;
清空标志位是为了给更多的操作空间,不止 sleep 有清空标志位的机制,很多方法都会这样
拓展小知识:
1.idea 生成的 try,catch catch 语句里面自动给的代码是打印调用栈,或者是抛出另外一个异常;实际开发中这两种代码只是占个位置而已
2.服务器的稳定性非常重要,若有异常通过catch语句对异常处理:1.在catch尝试自动回复;2.异常信息记录到日志;3.发出报警写邮件;4.少数正常业务逻辑依赖到catch
等待线程--join
1.当一个线程完成工作后才能进行下一步工作,需要一个方法明确等待线程的结束
2.虽然多个线程之间的执行顺序是不确定的,但是我们可以在应用程序中通过一些 api 来影响线程执行的顺序
3.join 就是一种方式,也是线程最核心的 api 之一,它通过影响线程结束的先后顺序来影响总的执行顺序
4.run方法中的内容执行时间不可预测
5.main 线程等待 t 线程,那就在 main 线程中调用 t.join()
6.执行 join 的时候,看 t 线程是否正在运行
如果 t 正在运行, main 线程就会阻塞(暂时不参与 CPU 执行)
如果 t 运行结束,main 就会从阻塞中恢复过来,继续向下执行
即阻塞让线程产生先后关系
7.在调用 join 的时候,可以看到它其实有三个重载的方法
(1)join()--死等:会导致程序卡住无法继续处理后续逻辑
(2)join(long mills)--带有超时间的等:等有一个时间上限(一般使用)
(3)join(long mills,int nanos)--设置一个ns级别时间(用处不大,系统无法精确到ns)
interrupt可以把阻塞等待的join提前唤醒
获取线程引用
Thread.currentThread()获取到当前线程的引用(Thread的引用)
若继承Thread,直接使用this拿到线程实例
若是Runnable或者lambda的方法,只能使用Thread.currentThread()
线程的状态
就绪:线程可以随时到CPU上执行,也包含正在CPU上执行
阻塞:线程暂时不方便去CPU执行
6种状态:
1.NEW Thread:对象创建好但没有调用start方法在系统中创建线程
2.TERMINATED Thread:对象存在但系统内部线程执行完毕
3.RUNNABLE:就绪状态--线程正在CPU上执行或者准备就绪随时去CPU执行
4.TTIMED_WAITING: 时间阻塞--一定时间会自动解除阻塞
5.WAITING:不带时间的阻塞,必须满足一定条件才会解除阻塞(join或wait都会进入)
6.BLOCKED: 由于锁竞争引起阻塞
状态可以在调试多线程代码bug作为重要参考依据;也可以调用jconsle查看进程中重要的状态和调用栈,通过·状态判定是否阻塞以及什么原因阻塞