作者主页:paper jie_博客
本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。
本文录入于《JavaEE》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将MySQL基础知识一网打尽,希望可以帮到读者们哦。
其他专栏:《MySQL》《C语言》《javaSE》《数据结构》等
内容分享:本期会对JavaEE中一个关于多线程的重要类Thread进行分享~
目录
什么是Thread
创建线程
继承Thread类
实现Runnable接口
匿名内部类创建Thread子类对象
匿名内部类创建Runnable子类对象
lambda表达式创建子类对象
Thread类的方法与常见属性
构造方法
常见属性
常用方法
启动线程 - start()
中断线程
引入标记
interrupt()
异常的原因
等待程序 - join()
获取当前线程引用 - currentThread()
什么是Thread
Thread类是在Java标准库中的,它可以视为是对操作系统提供的API进一步的抽象与封装. 一个Thread实例对象我们可以认为是一个线程.
创建线程
继承Thread类
这里需要继Thread类来创建一个线程类. 因为这里要重写run方法.里面就是这个线程需要执行的逻辑.
然后还需要创建它的实例,这样一个线程才算创建出来了. 最后需要调用start方法启动线程.
class MyThread extends Thread {@Overridepublic void run() {System.out.println("hello Thread");}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();System.out.println("hello main");}
}
实现Runnable接口
创建Thread实例,调用Thread构造方法时将Runnable对象作为参数.
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("线程运行代码");}
}
public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();System.out.println("hell main");}
}
匿名内部类创建Thread子类对象
这里后面是Thread类的匿名子类.
public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread("这是我"){@Overridepublic void run() {System.out.println("这是匿名方法");while(true) {System.out.println("heeh");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};t.start();}
}
匿名内部类创建Runnable子类对象
public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(new Runnable(){@Overridepublic void run() {System.out.println("匿名创建Runnable子类");}});t.start();}
}
lambda表达式创建子类对象
public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("lambda表达式创建子类对象");});t.start();}
}
Thread类的方法与常见属性
构造方法
这里第三个和第四个可以重命名,这样可以更好的进行调试.
常见属性
ID是这个线程的唯一表示,不同的线程ID是不会重复的
名称一般就是调试的时候可以用到
状态表示一个线程当前所处的情况.一般有就绪状态和堵塞状态
优先级表示线程是不是更容易被调度
前台线程的运行会阻止进程的结束,后台线程的运行不会阻止进程的结束.一个进程的结束需要等所有前台线程执行完才会结束,不然就算是main线程执行完了也不会结束.我们创建的线程默认都是前台线程.
是否存活简单来说就是run方法是否执行完了. Java中Thread实例虽然代表的是多线程,但是它的生命周期和内核中创建出来的线程PCB的生命周期不是一样的.
此时虽然Thread实例对象有了,但是内核中线程PCB还没创建,isAlive是false.只有在t.start()执行后内核中的PCB才会创建出来,这时isAlive为true
当run方法执行完后,内核中的PCB就销毁了,这时isAlive为false.
常用方法
启动线程 - start()
这里Thread实例对象虽然创建出来了,但是线程并未真正的启动.调用start()后才算真正的创建出了一个线程. 这里的start()才是在内核中创建出一个线程. 调用start()创建线程本质上就是调用系统的API来创建线程.
这里一个线程只能调用一次start(),再次使用start需要用另一个线程对象来调用.不然它会报出一个非法线程状态的异常.
public class ThreadDemo1 {public static void main(String[] args) {Thread thread = new Thread() {@Overridepublic void run() {System.out.println("线程");}};thread.start();thread.start();System.out.println("hell main");}
}
中断线程
中断一个线程就是提前让这个线程的run方法结束.常用的方法有两种:
1. 通过引入一个标记
2. 通过interrupt()方法
引入标记
这里通过flag这个标记来让run方法提前结束.当main线程执行到flag = true时,另一线程就会提前结束.
public class ThreadDemo2 {public static boolean flag = false;public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while(!flag) {System.out.println("hell Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});thread.start();try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("终止这个线程");flag = true;}
}
这里注意,flag标识是不能在main方法内中,不能作为局部变量.虽然lambda匿名内部类可以通过变量捕捉访问到外面的局部变量.但是这个局部变量必须是不可变的,是final修饰的,这就和我们需要通过改变标记来终止线程发生冲突了,这就不可行.
必须是final是因为main方法和Thread都有自己的函数栈帧.他们生命周期不同.flag是在属于main的栈帧中,一但main执行完了,它的栈帧就会销毁,Thread再想使用就用不到了. 这里变量捕捉就是为了这个而诞生.它就是传参,本质上就是在需要的线程上将flag拷贝一份.为了保证flag的一致性就让它不能改变.
interrupt()
Thread里面自带一个标志位.我们可以通过方法来获取和改变这个标志位.
这里可以用Thread.interrupted()或者Thread.currentThread().isInterrupted()来获取内置的标志位.
interrupt()方法可以改变标志位.
这里使用了Thread.currentThread().isterrupted:
public class ThreadDemo3 {public static void main(String[] args) {Thread thread = new Thread(() -> {while(!Thread.currentThread().isInterrupted()) {System.out.println("hell Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();try {Thread.sleep(4000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("中断它");thread.interrupt();}
}
但运行代码后我们会发现问题:
这里会发现它会抛出一个中断异常,然后继续运行,并没有停下来.
异常的原因
这时因为这里interrupt()方法提前唤醒了sleep,这个时候sleep就会做两件事:
1. 抛出InterruptedExecption这个异常
2. 将内置标志位还原.
所有这会导致线程继续运行.
需要线程停下来,处理方法我们在catch里面加上break就可以了.
这里在catch中我们有三种处理方法:
1. 让线程立刻停下来
2. 让线程先运行一些代码再停下来
3. 让线程不停下来继续运行
我们处理异常也有几种常见的方法:
等待程序 - join()
join方法可以调整线程执行的先后顺序.虽然说线程的调度执行是随机调度的.但这里join可以将线程进行堵塞从而影响到了线程的执行先后顺序.join所在的线程会发生堵塞,在调用join方法线程运行完后,这个线程的堵塞状态才会解除.
注意: 这里是调用join()的线程被等待先执行,join()所在的线程等待,后执行.
join()方法有三种:
第一种: 死等,需要等等待的线程执行完才会解除堵塞状态
第二种: 有时间的等待, 在一定时间内进行堵塞.超出时间范围就会解除堵塞状态
第三种:精确到微秒的有时间等待.
一般情况下,我们最常用的就是第二种情况:
public class ThreadDemo4 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while(true) {System.out.println("hell main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();thread.join();while(true) {System.out.println("hell ");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
这段代码,就是main线程进入堵塞状态,等待Thread线程结束. 虽然说Thread这个线程这执行中可以和其他多个线程共同进行调度执行,但由于main线程一直在等待,就算Thread线程在CPU上进行了多次切换也不影响这个线程先执行完.
这里注意: 我们的interrupt方法可以将join线程提前唤醒.
获取当前线程引用 - currentThread()
在使用类继承Thread创建线程方法我们可以用this直接引用这个对象.但是当使用lambda/匿名内部类/Runnable时this就不再指向Thread对象了.这时我们获取Thread对象引用就需要使用currentThread()方法了.
public class ThreadDemo7 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("hello Thread");System.out.println(Thread.currentThread().getName());});t.start();}
}
休眠当前线程 - sleep()
sleep可以将当前线程休眠一定时间,这个时间可以自己设定.但使用它需要抛出异常或者try- catch.这里休眠的时间因为线程调度的不可控,一般都会大于等于设定的时间.
它也有两种方法:
一般都是使用第一种,第二种是精确到微秒.
public class ThreadDemo8 {public static void main(String[] args) {Thread t = new Thread(() -> {try {Thread.sleep(1111);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("休眠1.111秒");});t.start();}
}