+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
🚀欢迎大家来到我的博客⭐
🚀本人是双非软工专业的一名大二学生,让我们共同努力,冲击校招!!!⭐
🚀本章博客介绍的是关于Thread的常用方法⭐
🚀一下是我的QQ号,欢迎大家来进行技术交流!!⭐
🚀QQ:2641566921⭐
🚀以后会更新一些笔试有关的题目,请大家多多关注⭐
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
目录
4、Thread(Runnable target,String name)
Thread.currentThread.isInterrupted()
Thread到底是个啥??
Thread在英文中翻译为线,线程,在我前面的博客【多线程2】多线程的创建_sekiro&mikasa的博客-CSDN博客 中较为详细的介绍了Thread类的使用方法。
线程是操作系统进行调度的基本单位,每一个运行中的程序都是一个线程,进程之中包含着一个或者多个线程,线程之间可以通过操作系统的调度进入cpu执行自己的程序,每一个线程都有一个自己的执行流。
———————————————————————————————————————————
我们在Java程序中开启一个线程,我们程序员是如何对这些线程进行操作的呢,这就需要我们的Thread类对象来封装一些关于操作系统的api来为我们程序员操作线程提供支持。
Thread的构造方法
Thread的构造方法有很多种,我们就挑其中最重要的几种来进行说明
1、Thread()
这个构造方法没有指定任何的参数,它仅仅是创建了一个线程的对象(Thread对象),是个无参构造。
Thread thread = new Thread();
这个方法会报告一个警告,提示你没有重写Thread里面的run方法。
2、Thread(Runnable target)
这个方法其实就是通过传递一个Runnable接口进行线程的创建,在Runnable里面应该把run方法重写。
class Myrun implements Runnable{
@Override
public void run() {
System.out.println("hello");
}
}
public class Demo1 {
public static void main(String[] args) {
Thread thread = new Thread(new Myrun());
thread.start();
}
}
3、Thread(String name)
这个方法可以给指定的线程赋予一个单独的名字
Thread thread1 = new Thread("name");
我们给thread1起了个名字叫name的线程 。
4、Thread(Runnable target,String name)
这个很好理解,不过是将2、3的代码进行结合,就会得到一个通过Runnable接口实现run方法,还可以进行重命名的进程。
Thread的几个常见的属性
属性 | 对应的获取方法 |
id | getId() |
名称 | getName() |
状态 | getState() |
是否存活 | isAlive() |
优先级 | getPriority() |
是否被中断 | isInterrupt() |
是否后台线程 | isDaemon() |
1、getId()
一个线程会有自己的专属的身份标识,我们可以通过调用这个方法来获得这个线程的身份标识。
他的返回值是long。
2、getName()
这个方法可以进行线程名称的,在构造方法中我们有个Thread(String name)的方法,其中构造方法中设置的名称就是现在getName调用的名字。他的返回值是String。
3、getState()
我们之前的文章中提到过线程的状态有运行态,阻塞态和就绪态。返回值是Thread.State
4、isAlive()
它的返回值是boolean类型的变量。这个方法可以判断线程是否存活,在多线程代码中,每个线程的存活时间是有差异的,通常来说,当我们的Thread的实例对象调用了start之后,才会在操作系统中创建一个PCB,这个时候我们认为这个线程是存活的,返回值是true,存活状态会一直保持到线程被销毁,那么什么时候进程会被销毁呢?当我们的重写的run方法结束的时候,我们的线程就会被销毁,此时再调用isAlive就会显示false。
5、getPriority()
这个方法可以用来返回线程的优先级,返回的是一个int类型的值,他的值越大就说明他的优先级别越高,当然他还可以设置优先级,不过设置的优先级并没有什么用处,因为决定他的优先级的还是操作系统。
6、isDaemon()
这个方法会判断这个线程是不是守护线程。
那么什么是守护线程呢?
其实守护线程也可以称之为后台线程,我们先来区分一下前台线程和后台线程,前台线程是指一个进程当中,会影响进程结束的线程,当这个线程运行的时候进程不会结束,会一直执行线程。
后台线程与前台线程相反,它不会影响进程的结束。称之为守护线程。
包括main方法的主线程在内,代码创建出来的线程都是属于前台线程,如果我们想创建一个后台线程,就需要使用setDaemon()方法来进行线程的设置。
我们可以看到,我们创建的一个线程被死循环的执行,我们创建的线程是一个前台线程,会阻止进程的结束。
我们通过setDaemon()方法将前台线程更改成为后台进程。
我们可以看到虽然我们创建的线程没有执行和完毕,但是进程可以正常地结束,所以这就是前台线程和后台线程的区别。
以下是后台线程的代码
class MyThread implements Runnable{
@Override
public void run() {
while(true){
System.out.println("hello,Thread");
}
}
}
public class Demo2 {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.setDaemon(true);
thread.start();
}
}
线程的终止与中断
我们要学习线程的终端就需要分清楚线程的终止与终端的概念。
线程的终止
线程的终止问题其实就是一个线程如何的去结束他的生命周期,一般来说,一个线程的终止会有三种情况。
1、线程正常结束:线程的run方法结束之后完成了他的工作,线程就自动结束了。
2、线程异常结束:线程在执行的过程中发生了未捕获的异常,导致线程以外的终止。
3、线程意外结束:程序由于某种不可抗力因素(如断电,系统崩溃)意外结束。
线程的中断
而线程的中断其实是线程终止的一种机制,是一种协作式的线程终止方法,一个正在执行的线程A可以被另一个线程B中断,引起正在执行的线程A的终止,而这种中断的请求是由线程B发起的,而线程A可以根据自己的自身情况来决定是否要中断自己。
由于中断我们可以理解为线程终止的一种方法,而线程终止的一种情况是线程正常的结束,所以我们只要在另一个线程中控制正在运行的线程执行完run方法就可以中断一个线程了。
我们先来简单粗暴的进行一次线程的中断。
public class Demo3 {
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while(flag){
System.out.println("hello");
}
});
thread.start();
Thread.sleep(1000);
flag = false;
}
}
通过构造一个标志flag,在main线程中更改flag的值就会引起另一个线程的中断。
Interrupt()
Interrupt介绍
在Java中,线程中断是通过Thread类的interrupt()方法来实现的。当一个线程调用另一个线程的interrupt()方法时,被中断的线程会收到一个中断请求,并有机会在合适的时机停止执行。被中断的线程可以通过检查自身的中断状态来决定是否终止执行,并在适当的时候抛出InterruptedException异常。需要注意的是,线程中断并不会直接停止线程的执行,它只是向线程发出一个中断请求。被中断的线程需要在执行过程中自行检查中断状态,并决定是否终止执行。另外,线程中断只是一种协作式的终止方式,它不能强制终止线程的执行。
Interrupt本质上来说就是一个线程内部自带的标志位,用来决定线程是否被中断,Thread.currentThread.isInterrupted()就是检查自身中断状态的一个方法。中断状态是判断线程是否被中断的一个值。
Thread.currentThread.isInterrupted()
1、Thread.currentThread()
这个方法会获得正在Cup上运行的线程的对象
2、isInterrupted()
isInterrupted()是Thread类的一个实例方法,用于检查当前线程是否被中断,它并不会清除线程的中断状态。如果当前线程被中断,则返回true,否则返回false。
3、Interrupt()
interrupt()是Thread类的一个实例方法,用于中断当前线程或者指定线程。当一个线程被中断后,它的中断状态会被设置为true。
Interrupt会做两件事
1、把线程内部的中断状态更改为true
2、如果线程处于sleep状态,会把线程唤醒。唤醒之后,sleep()会继续通知线程运行,并且把线程内部的布尔变量改变成false(中断清除)。
代码演示
接下来我们使用代码进行演示
class InterruptThread extends Thread{
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
System.out.println("Thread is running");
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread is Interrupted");
throw new RuntimeException(e);
}
}
System.out.println("Thread is stopped");
}
}
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new InterruptThread();
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
我们来分析一下结果:
我们可以看到,在线程启动之后,main线程被中断了5秒,thread线程的打印速度是1秒一次,当进入循环的时候,因为线程没被终止, Thread.currentThread.isInterrupted()返回的值是false,取反之后判断为true,所以进入循环打印五次,当主线程执行到interrupt的时候,循环判断结果为false,会抛出一个InterruptedException异常。
现在我们对这个异常进行一下处理,如果我们删除throw new RuntimeException(e);这段代码,进程会不会终止呢?
我们可以分析一下现在的结果
我们可以看到,删除throw new RuntimeException(e);之后,线程会被InterruptedException捕获,但是没有停止,会继续运行。
这是因为当线程在执行过程中收到中断请求并抛出InterruptedException异常时,它的中断状态会被自动清除,即调用isInterrupted()方法会返回false。这意味着,如果在处理InterruptedException异常的过程中需要检查线程的中断状态,必须在catch语句块中重新设置线程的中断状态,以确保中断状态不丢失。
也就是说在触发了中断请求之后,sleep把中断状态给清除了,所以下一次进入循环判断还是会为true,也就会无限循环,中断操作就会失效。
改变后的代码:
class InterruptThread extends Thread{
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
System.out.println("Thread is running");
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread is Interrupted");
Thread.currentThread().interrupt();
}
}
System.out.println("Thread is stopped");
}
}
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new InterruptThread();
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
运行结果我们呢可以看到它正常退出了。
等待线程 join方法
join方法介绍
因为线程之间是抢占式执行的,我们不知道下一刻哪一个线程会被调度到cpu上执行,为了程序员更好的控制线程执行的顺序,我们引入了join这一方法。
join是Thread的一个实例方法,表示一个线程等待另一个线程执行完成,当一个线程A中调用另外一个线程B的join方法的时候,线程A就会被阻塞等待,直到线程B执行完成在之后,线程A才会开始执行。
需要注意的是,join()方法可能会抛出InterruptedException异常,因此在使用join()方法时需要进行异常处理。如果线程在等待期间被中断,则join()方法会抛出InterruptedException异常,并清除中断状态。
另外,如果一个线程的join()方法被调用多次,只有第一次调用是有效的,后续的调用会被忽略。
join中存在几个值得注意的问题:
1、join其实就是让线程主动退让cpu的使用权限,如果一个线程已经完成执行(即线程的run()方法已经执行完毕),那么在其他线程中调用该线程的join()方法时,该join()方法会立即返回,不会阻塞其他线程。因为一个已经完成执行的线程已经不再是一个活跃的线程,其他线程也没有必要等待它完成。在这种情况下,调用join()方法只是一个无效的操作,不会对程序的执行产生任何影响。
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println("start");
});
thread.start();
Thread.sleep(1000);
thread.join();
System.out.println("end");
}
}
2、join之后,处于运行状态的线程不代表着会阻止其他线程的执行,自己霸占着cpu资源,只是阻塞了调用该线程join方法的线程。
3、join还有指定等待时间的方法,当另一个线程执行时间超过join方法里面规定的时间的时候,无论这个线程有没有执行完毕,都需要回到就绪队列接收调度。
4、当指定等待时间之后,线程在等待时间内很早就完成了任务,被阻塞的进程不会傻傻的等待时间全部结束之后再执行,而是当join的线程执行完毕之后会立即执行自己,此时剩余的等待时间无效。
我们可以看到,线程很快的执行了end。
join的实际小用法
我们让两个变量各自增加10亿次,算一下系统所用的时间
单线程:
public class Demo6 {
public static void main(String[] args) {
int a = 0;
int b = 0;
long start = System.currentTimeMillis();//获得系统的时间戳
for(int i = 0; i< 10000_00000; i++){
a++;
}
for(int i = 0 ; i < 1000_00000; i++){
b++;
}
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
接下来我们看一下多线程代码:
public class Demo7 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
int a = 0;
for(int i = 0; i < 1000_00000; i++){
a++;
}
});
Thread thread1 = new Thread(()->{
int b = 0;
for(int i = 0; i < 1000_00000; i++){
b++;
}
});
long star = System.currentTimeMillis();
thread1.start();
thread.start();
try {
thread1.join();
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
System.out.println(end-star);
}
}
那么多线程这么好用,我们能不能尽量多的使用多线程呢?
我们需要注意,这是由范围要求的,此处的区间范围内,两个线程并发执行,效率会比单线程的要高不少。但是并不一定是,所有的区间范围内,把一个大的任务拆解成多个任务,让不同的线程并发执行,多线程都比单线程执行效率高。
因为操作系统的调度也需要很多的时间,如果我们编写的线程过多,就会导致调度会更加频繁,也不利于我们缩小运行的时间。