目录
一、线程基本属性及获取方法
1、id和name
需要注意的是:这里的id跟pcb中的id是不同的,是jvm自己创建的一套体系;name可以是线程默认的名字也可以在创建线程时自己赋予其名字。通过getId()和getName()来获取。
2、状态
线程的状态:就绪状态和阻塞状态,通过getState()获取。
3、优先级
虽然java提供了优先级接口,就算修改了优先级现象也不明显,系统调度时还是以系统本身的优先级为准。通过getPriority()获取。
4、是否后台线程 isDaemon();
前台线程:这样的线程不结束的话java进程是一定不会结束的。
后台线程:这样的线程即使在运行也无法阻止java进程结束。
在java代码中,主线程是前台线程,程序员手动创建的线程默认也是前台线程,可以通过setDaemon设置为后台线程,通常情况下设置为后台线程的原因是:不希望该线程影响进程结束。
5、是否存活isAlive()
指的是系统中的线程(PCB)是否还存在,Thread对象的生命周期和线程是完全不一样的,俗称”我生他未生,他生我已老“,通常情况下Thread对象的诞生是比线程要早的,比如说下图
这段代码仅仅实例化了Thread对象,此时内核中的pcb对象还未创建,进行t.start操作时pcb才被创建;而对象的销毁跟线程间也是参差不一的,如图:
因为此时t线程中需要执行的任务什么也没写,所以在t线程运行的瞬间就会结束,内存中的线程和pcb就被销毁了,但在sleep执行结束之前,t还是指向Thread创建的对象的,并没有被GC回收掉,此时,线程结束了但t仍然存在。有时在主线程中手动将t指向空也许t线程没结束而对象已经销毁了,所以Thread对象的生命周期与系统中线程的生命周期是不一致的,不能说谁长谁短。可以通过isAlive来判定系统中的线程是否还存在。
5、中断(重点)
中断一个线程,在java中只能起到提醒的功能,真正决定要不要终止线程的,还是线程本身,因为线程之间的调度是随机的,如果一个线程正在做一项重要的工作被其他线程终止了,可能会引起一些bug。
中断线程的核心思路是:让需要终止的线程的入口尽快结束
第一种方法:自己手动实现
在while的判断条件中设置循环条件
public class demo7 {
public static boolean isRun = true;
public static void main(String[] args) {
Thread t = new Thread(()->{
int count = 0;
while(isRun){
count++;
isRun = false;
}
});
t.start();
}
}
当isRun为false时线程就被手动中断了。
第二种方法:使用Thread提供的interrupt和isInterrupt方法来实现上述效果
刚才我们是通过设置一个boolean变量来控制线程的中断,实际上Thread里面内置了一个标志位,功能更强大
标志位:boolean变量,true表示要中断,false表示要继续进行
循环条件是通过t.isInterrupt()获取当前线程的是否中断,在线程中使用t.interrupt相当于将boolean变量设置为true,该方法相对于上一个方法强大之处就在于:
可以唤醒sleep等阻塞方法,即使sleep十秒,但刚休眠一秒,会强行唤醒然后中断;按照第一种写法,必须再等待九秒线程才能继续循环判定,线程才能结束,而第二种写法会让sleep立即抛出InterruptExpected异常,不会再等待,立即被唤醒。
主线程sleep三秒后将t线程的isInterrupt设置为true,执行结果为t线程执行三次后sleep抛出异常,实现代码如下:
public class demo7 {
public static boolean isRun = true;
public static void main(String[] args) {
Thread t = new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt();
}
}
但需要注意的是:已知interrupt会使sleep强行唤醒,但当触发interrupt时正在sleep休眠阶段,sleep被唤醒的同时也会清除标志位,也就是抛出异常后循环因为清除了标志位会继续循环,
这个功能是为了把决定权交给程序员自己,由程序员决定下一步是中断还是继续,想等一会再结束可以在catch方法内写一些功能然后break,想立马结束可以直接break就好了。
二、线程等待
多个线程在系统中的调度顺序是随机的(抢占式执行),作为程序员希望程序的结果是稳定的,不应该是随机的,所以在随机的体系中加入一些控制,让结果不那么随机,通过线程等待就是要确认线程结束的先后顺序。
在刚才的代码中,main线程和t线程之间的先后顺序是不确定的,如果想让t线程先结束,main后结束,可以在main线程中用线程等待(join)。main线程中调用t.join就是让main线程中等待t线程结束,main再结束
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
for (int i = 0; i < 3 ; i++) {
System.out.println("t run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t end");
});
t.start();
t.join();
System.out.println("main end");
}
main线程调用join方法有两种可能:
1、t线程已经结束,join立即返回
2、t线程未结束,join就会阻塞等待,一直到t线程结束,join才能解除阻塞,立即执行
主线程可以调用多个join,先执行他t1join,t1线程结束后主线程执行tjoin,等待t线程结束,t与t1谁写在上和下效果一样,总的等待时间都是t与t1的运行时间,注意:线程是并发执行的,t与t1是各自调度各自执行,执行的顺序和谁先结束都是不一定的,与具体的实现有关,可以知道的顺序只有主线程在t和t1执行结束后才能结束。
join的另一个要点:join后的括号刚才都是无参数版本,线程不结束不返回,主打”死等“,加上参数后传入的时间就是最大的等待时间,实际开发中使用无参数死等法的情况较少,因为容错率很低。
三、线程的状态
java对线程的状态大概分成了六种,可以通过getState来获取:
1、NEW
Thread对象已经有了,但还没通过start调用,系统中的线程还未创建。
2、TERMINATED
线程已经终止,内核中的线程已经销毁,Thread对象还在。
3、Runnable
线程处于就绪状态,两种情况:(1)线程正在cpu上执行 (2)线程没在cpu上执行但可以随时调度到cpu上执行。
4、WAITING 死等导致的阻塞
5、TIMED_WAITING 带有超时时间的等
6、BLOCKED 进行锁竞争产生的阻塞
456都是阻塞状态,可以通过jconsole来查看线程状态,找到jdk进入bin文件就可以看到jconsole
双击运行,连接;(如果线程处一个线程都没有可以通过管理员身份运行)
连接后可以看到很多的线程,可以看一下main线程,此时是TIMED_WAITING状态
在实际开发中,后续发现哪个线程卡死了,这个需要关注线程状态,通过状态就可以看到线程是在哪一行代码产生了阻塞。
本篇文章到此结束,希望可以帮助到每一位观看本篇文章的人。
感谢观看
道阻且长,行则将至。