深入解析Java线程:创建、状态与中断机制【个人心得】

张开发
2026/4/9 14:12:32 15 分钟阅读

分享文章

深入解析Java线程:创建、状态与中断机制【个人心得】
在前面我们已经简单的了解了一下什么是进程和线程什么是上下文切换什么是异步调用等知识点那么加下来就深入的去聊一聊有关于线程的那些事准备好了吗马上发车内容可能相对比较多可以使用目录栏来跳转阅读自己感兴趣的部分创建和运行线程创建线程的方法方法一如果需要创建一个线程的话我们可以考虑直接使用new方法new一个线程出来接着是利用内部类的写法在里面加上这个线程在运行过程中需要执行的方法是什么最后是通过 start调用的方式来启动线程【交给任务调度器去到操作系统里面进行执行】当然我们也可以使用setname的方式来给线程起一个名称Slf4j public class demoThread { public static void main(String[] args) { Thread t1new Thread(){ public void run(){ log.debug(running); System.out.println(创建了线程); } }; t1.setName(t1); t1.start(); log.debug(running); } }方法二需要在Runnable里面写上对应的方法接着是新建一个线程然后调用这个线程来运行runnable里面的任务即可start开启线程Slf4j public class test2 { public static void main(String[] args) { Runnable rnew Runnable() { Override public void run() { log.debug(running); } }; Thread t2new Thread(r,t2); t2.start(); log.debug(running); } }一种简化上的写法在这里看到了这个注解FunctionalInterface代表的是在接口里面只实现了这一个抽象方法因此我们可以考虑用lanbuda表达式的方式来进行简化处理方法三在这里如果用到的是Callable类型的参数的话是可以获取到线程运行的结果的然后将这个结果进行返回即可Slf4j public class test3 { public static void main(String[] args) { FutureTaskInteger tasknew FutureTask(new CallableInteger() { Override public Integer call() throws Exception { log.debug(running); Thread.sleep(1000); return 100; } }); Thread tnew Thread(task); t.start(); } }Thread和runnable之间的关系解释一下源码在跟踪源码的过程我们可以发现这里的target指的就是runnable这个对象如果这个对象不为空的话那么就执行对象内对应的代码第二种方法相当于是继承了父类的run方法并且进行了重写的操作但是在这个部分小编多唠叨一句在这个部分的话更推荐的是用runnable的方式来实现因为runnable本身是一个接口只需要对应的类去implement这个接口就可以了然后实现内部的方法创建一个线程如果是用Thread的方式的话就需要去继承Thread这个类但是在JAVA当中类一般都是单继承的机制如果这个类已经继承了其他的类了就不好再去继承Thread这个类了因此我们更加推荐用实现接口的方式来新建线程callable的话就结合咱具体的业务需求来进行使用如果是需要线程执行返回的结果的话就用callable这个类型来实现观察多个线程同时运行查看和杀死进程线程运行的原理简介这个部分就有点类似于计算机系统导论部分的知识点栈帧的调用问题先进后出的思想A调用了BB调用了CC在执行完了之后退出接着是B退出的一个流程B调用C的时候会保存B的返回地址在C执行完成了之后释放内存返回到B栈帧是以线程为单位的每个线程都有它自己独立的一套栈帧相互之间不会影响线程上下文切换上下文切换主要是运用在线程之间的切换上当一个优先级更高的线程需要执行的时候/需要进行垃圾回收的时候就需要其他的线程靠边站进行上下文切换操作程序计数器会记住下一条指令的地址因此会有一个保存地址的功能保证你进行上下文切换的时候完成了工作还会返回到原来的位置继续往下执行下去频繁的上下文切换会影响系统的运行效率因此线程的数量并不是越多越好常用方法在这里主要是注意一下start和run这两个启动线程方法的区别start和run方法介绍如果我们新建了一个线程对象直接执行run方法的话是在main线程里面执行的他并没有异步的调用线程达不到提升性能和异步执行任务的要求start就可以实现异步的操作【不能多次的进行调用】sleep和yield解释一下sleep在执行过后线程会进入到一个Timed Waitng状态并且需要经过sleep里面的这段时间之后才会接着进入到就绪状态在Timed Waitng状态期间任务调度器是不会考虑阻塞状态的线程的睡眠结束后的线程并不是立刻执行的是需要等到CPU给他分配时间片的时候才会去执行yield在执行后线程会主动将执行权释放从running进入到runnable就绪状态当然让出线程的方式不一定能够把当前线程的执行权真的给让出去这是要由任务调度器来决定的yield方法执行的话相当于是给任务调度器提一个建议要不要把我当前的CPU时间片让出去给其他的线程去执行更好一点相当于只是提一个建议由任务调度器来决定是否要执行解释状态因为main方法在t1线程之前就执行了然后再进到t1里面的那一刻t1还没有进入休眠状态就是代码还没有跑到sleep阶段这就是为什么呈现了runnable状态第二种方法就是让主线程先休眠个500ms然后进入到线程t1里面去运行这个时候t1会sleep2000ms当主线程醒过来的时候t1线程还在休眠状态第三种的话使用到了一个关键字interrupt进行打断中断线程的休眠状态强制唤醒那此刻线程是会抛出一个异常后面更推荐的是用这种方式来代替sleep因为这种方式的话可以指定时间的单位而Thread里面的sleep方法是没有办法去指定单位的默认就是休眠多少ms解释一下在这个部分线程优先级和yield都是提示给任务调度器就是给任务调度器一个建议但是并不一定会采纳但是对于时间片的分配还是会有一些影响【一部分采纳一部分不采纳】join解决方法如果使用sleep关键字来解决的话需要考虑到的一个问题是我们并不知道线程t1会运行多久如果我们的sleep参数设置的不合理的话可能会导致等待时间过长/过短造成性能的浪费那我们可以考虑使用join这个关键字来解决join的话还有一个等待时间的概念如果线程没有在等待的时间内完成任务那么就不等了接着往下去运行后续的代码如果线程在等待的时间内完成了任务那么就直接去运行后面的代码都完成任务了就不需要再花时间等到join设置的时间段结束了代码解释线程12同时启动先对线程1进行join操作然后是线程2join操作但是最后只等待了2s【一共2s】因为线程12同时启动t1的启动并没有影响到t2这两个相当于是在并行执行如果是先调用t2的join那么最终结果还是2s因为t1的join在背调用的时候已经执行完成了interrupt可以打断阻塞状态的线程和运行状态的线程代码解释如果是需要打断正在运行过程中的线程直接的去执行interrupt是没有办法直接把它打断的这个时候就需要我们在线程内部加上一个对应的打断标记如果在主线程中执行了interrupt那么对应的标记就会变成true从而执行打断的操作两阶段终止模式错误的思路:如果使用stop的方式来杀死线程的话会导致该线程占有的资源无法进行释放如果这部分资源其他线程也需要的话有可能会导致死锁的问题System.exit(int)用这个方法来停止线程的话会导致这整个的程序都会停下来Java程序直接暂停这种做法要是用在项目开发当中是绝对不可能允许的整个的流程图解释整个的程序是放在while(true)下面运行的实现一个实时监控的功能首先是判断是否被打断如果被打断则可以在break之前执行一部分代码来料理后事break结束循环如果没有被打断则进入到sleep阶段这样子可以避免对CPU占用过高睡眠结束后就去检查一下当前程序/进程是否被打断传递打断信号如果没有在睡眠阶段那么对应标识符就是true直接传递过去就可以打断如果是在睡眠阶段被打断抛出了一个中止异常那么就需要程序对这个异常先进行一个处理处理完成后再执行interrupt操作。在睡眠阶段进行打断操作的话是需要执行两次interrupt操作可以把第一次interrupt操作理解为打断了线程的睡眠阶段第二次打断才是真正的interrupt这个线程因为如果被打断线程正在sleepwaitjoin会导致被打断的线程抛出异常并清除 打断标记微解释一下如果考虑情况1就是在线程的睡眠阶段被打断那么会抛出异常我们可以考虑在异常处理里面再进行一次打断操作即可正常退出如果考虑情况2就是在睡眠阶段外被打断就在下一次循环的过程中去打断不用再去进行第二次打断interrupt打断标记为真时会让park失效不推荐的方法主线程和守护线程在这个部分主线程内只要有一个进程还在正常的运行那么整个的Java程序就不会结束。守护线程是一种特殊的线程只要其他的非守护线程运行结束了即使守护线程内部的代码么有执行完也会被强制结束解释垃圾回收在jvm虚拟机的堆内部会分配很多的对象这些对象如果没有被及时的使用那么间隔一段时间后就会被回收tomcat里面的两个线程是tomcat用来接收请求和分发请求的线程线程的状态五种状态六种状态TIMED_WAITING可以使用sleep来实现这个状态有时限的等待WATING的话就需要考虑没有时限的等待需要一直等下去的那种BLOCKER的话如果调用了一个已经上锁了的资源是无法进行调用的因此会呈现BLOCKED的状态总结前面讲了那么多那么在这个地方就做个小结吧线程的创建部分我们需要知道的是线程创建的三种方法这三种方法之间的联系与区别需要结合实际的业务需求来选择对应的创建线程的方式接着是线程当中重要的API就是线程当中的哪几个关键字需要知道怎么去用就好线程的状态这个部分的话需要知道使用哪些API会导致线程进入一个什么样的状态关键字和状态的对应关系线程状态之间的相互转换线程的应用方面需要有一个了解知道怎么做可以保证线程的安全性提高系统性能原理方面栈栈帧上下文切换程序计数器【这个部分的话就涉及到了JVM部分的知识计算机基础知识操作系统的知识了感兴趣的友友可以去深入了解】模式两阶段终止模式

更多文章