此篇文章延续上一篇文章,与大家分享Thread常见的方法以及线程的状态相关知识
其他内容我们下一篇再见!
如果有错误或不足请您指出!!!
目录
- 3.Thread类及常见方法
- 3.1Thread常见的构造方法
- 3.2Thread常见的几个属性
- 4.中断一个线程
- 4.1自己实现控制线程结束
- 4.2调用Thread提供的interrupt方法
- 5 .等待一个线程
- 6.获取当前线程的引用
- 7.线程的状态
- 7.1 new
- 7.2 terminated
- 7.3 Runnable
- 7.4阻塞
- 7.5查看当前线程的状态
3.Thread类及常见方法
3.1Thread常见的构造方法
(1)String name,就是在创建线程的时候,给线程起个名字,实际上是否有名字对线程本身没有任何影响,即使你不起名字,线程也会有默认的名字:Thread-0,Thread-1…,只不过起了名字后,当进程执行的过程中,可以通过jconsole / idea看到不同线程的名字,出现问题的时候,更加直观的把问题线程和代码关联起来,方便调试
public class Test1 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while(true){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"自定义线程");t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}
(2)对于ThreadGroup,在开发中很少见到,有的时候,希望把多个线程进程分组,分组之后,就可以针对不同的分组进行批量控制
但是这种写法目前在实际开发中更多的是被线程池取代了
3.2Thread常见的几个属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
(1)id 这里的id和pcb里面的pid是不一样的,这里的id是jvm自己搞的一套id体系,而我们通过java代码是不能获取到pcb里面的id的
(2)优先级
实际上虽然iava而提供了修改优先级的接口,但是即使你修改了优先级,现象也是不明显的,因为你修改了优先级是一回事,系统调度又是一回事,这里你修改了优先级只能说是一种"建议",具体还是以系统为主
而不仅是在java,即使你通过c去调用系统原生的修改优先级的api,效果也是不明显的,本质上还是因为调度这个事情,操作系统就一堂言了,程序员很难干预到
(3)前台线程和后台线程
对于前台线程,这样的线程不结束的话,java进程是一定不会结束的
对于后台线程,这样的线程即使继续执行,也是无法阻止java进程结束的
前台线程是可以有多个的,但是是有是当最后一个前台线程结束后,进程才算真正结束
在java中,main就是前台线程,而我们自己创建的线程默认也是前台线程,至于其他jvm创建的线程就是后台线程了,比如GC,GC是要周期性持续性的运行的,如果这样的线程设置为前台线程,那么进程就再也结束不了了
如图所示,main线程和t线程都是前台线程的时候,即使main结束了,进程也没有结束,必须等到t线程结束,进程才结束
在java中,我们可以通过setDaemon来将线程设置为后台线程
此时由于只有main线程是前台线程,那么一旦前台线程结束后,进程也就结束了
注意:关于线程属性的所有设置都要放在start之前,如果在start之后设置,那就来不及了
(5)是否存活
指的是在系统中的pcb(线程)是否存活
值得注意的是,线程的生命周期和java中Thread对象的生命周期不一定完全一样的
我们来对比一下下面的两段代码
①
public class Demo5 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {});t.start();Thread.sleep(1000);}
}
此时由于T线程里面没有做什么事情,因此操作系统内核里面对应的pcb一下子就销毁了,但是由于主线程调用了sleep,在sleep结束之前,Thread对象是不会销毁的
②
public class Demo5 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();t = null;}
}
此时在t线程结束之前,t线程指向的对象就要被GC回收了
因此,对于线程的生命周期和线程对应对象的是生命周期是不一样的,不能说谁长谁短,那么我们就可以利用isAlive来判断系统中的线程是否还存在
4.中断一个线程
中断线程,在java中也只是"提醒、建议"真正要不要终止,还是要线程本身来决定的
即假设t线程正在执行,其他线程只能是提醒一下t线程是否要终止,t收到这样的提醒后,也是靠自己决定要不要终止
4.1自己实现控制线程结束
private static boolean isRunning = true;
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while(isRunning){System.out.println("t线程运行");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t线程结束了");});t.start();//3s后,让t线程结束Thread.sleep(3000);System.out.println("控制t线程结束");isRunning = false;
}
但是假设这里t线程的sleep时间是10s,此时main线程是无法让t线程即使终止掉的
4.2调用Thread提供的interrupt方法
我们刚刚是自己定义的一个boolean变量,实际上Thread里面内置了一个,使用内置的标志位,功能更加强大
interrupt(): 这是一个实例方法,用于中断线程。当调用某个线程的 interrupt() 方法时,会给该线程设置一个中断标志,表示该线程已被请求中断。如果线程处于阻塞状态(如等待、睡眠或在输入输出操作中被阻塞),它会收到一个 InterruptedException 异常,以此来响应中断请求。在其他情况下,需要检查线程的中断状态,并采取适当的行动来响应中断请求。
interrupted(): 这是一个静态方法,用于检查当前线程是否被中断,并且会清除线程的中断状态。如果调用线程的 interrupted() 方法返回 true,则表示该线程之前被中断过,并且清除了中断状态;如果返回 false,则表示该线程之前未被中断过。通常,这个方法用于检查当前线程是否被中断,并在必要时做出相应的处理。
isInterrupted(): 这是一个实例方法,用于检查线程是否被中断,但不清除中断状态。调用线程的 isInterrupted() 方法返回 true 表示该线程被中断,返回 false 表示未被中断。通常,这个方法用于检查其他线程的中断状态,而不影响当前线程的中断状态。
当一个线程正在sleep的时候被中断,此时就会抛出InterruptedException异常,即立即唤醒线程,不会再等待了
但是如果我们不采用抛出异常的方法:
怎么t线程还会继续执行???
实际上是由于sleep,如果调用interrupt方法后,线程没有sleep,确实是直接修改标志位就完了
但是如果正在sleep,那么就会立即唤醒sleep,但sleep在被唤醒的同时,也会清除刚刚的标志位!!!
之所以要清除标志位,实际上也就是要将控制权交给程序员自己,当前代码是要继续执行?还是直接结束,程序员就可以通过代码来决定了
5 .等待一个线程
在操作系统中.多个线程之间的调度顺序是随机的(抢占式执行),但是我们往往希望线程的执行顺序是稳定的,因此我们希望在随机的体系上,让结果变得不那么随机
具体来说,一个线程数什么时候调度是不确定的,多个线程谁先开始谁先结束是不确定的
我们通过线程的等待,就是要能够确线程结束的先后顺序
如果不加join方法,那么main线程和t线程谁先结束是不确定的
但是在main线程里面调用t.join,就是要让main线程去等待t线程,也就是t线程结束后,main线程才继续执行下面的逻辑
此时就确保了main线程一定是后结束的那一个线程
注意:此时main线程调用t.join方法就会有几种情况
(1)如果此时t线程已经结束,那么join方法就立即返回,不会阻塞main线程
(2)如果t线程还没结束,那就一直等待到t线程结束
(3)如果t线程还没开始,也是直接返回
实际上join也提供了带超时的版本
只带一个参数的join,代表等待N ms
带两个参数则精确到毫秒和纳秒
(但是实际上纳秒这个级别的时间,对于主流系统来说,都是太精细了.像windows / linus这样的系统,无法精确到ns级别的时间 甚至到了ms级别都有可能出差错.而且达到ns这种级别的系统开销太大了)
传入的时间则代表最长的等待时间,比如写了10ms,如果10ms还没到,t线程已经结束了,那么就直接返回
如果10ms到了,t线程还没结束,那么就不等了,继续往下走
这种场景应用还是非常广泛的
假如A中的主线程创建t线程,t线程给B发送请求,紧接着A就让主线程t.join等待t线程结束(获取到B响应)
如果B挂了.此时如果是使用死等的策略,就会使A中的主线程卡住了,无法再继续执行后面逻辑了
这种情况就需要制定好超时时间,超过时间就不等了,继续做该做的事情
6.获取当前线程的引用
public static Thread currentThread();
返回的是当前线程对象的引用
7.线程的状态
我们在上一章节谈到的进程的状态实际上是线程的状态或者叫做pcb的状态
在Java中,对线程的状态大体是分成6中不同的状态
7.1 new
表示Thread对象已经有了,但是还没有start,即操作系统内核的pcb还没创建
7.2 terminated
表示线程已经终止了,即内核中的线程已经销毁了,但是Thread对象还在
7.3 Runnable
分成两种
(1)指的是当前线程正在cpu上执行
(2)指的是这个线程虽然没有在cpu上执行,但随时可以调度到cpu上执行
7.4阻塞
(1)waiting 死等进入的阻塞
(2)timed_waiting 带有超时版本的阻塞(sleep属于其中的一种)
(2)blocked 进行锁竞争时候产生的阻塞(后面会谈到)
7.5查看当前线程的状态
(1)getState
(2)jconsole