一、背景
在《分布式锁主动续期的入门级实现-自省 | 简约而不简单》中通过【自省】的方式讨论了关于分布式锁自动续期功能的入门级实现方式,其中介绍了如何通过一个额外的线程来完成主动推迟分布式锁的过期时间,其中提到了几个线程操作的核心技能点,如 interrupt
、sleep
、volitale
...,有个刚入行的朋友特别细腻,看完之后表示好像懂了又好像差点什么,于是跟笔者又探讨了一些线程的知识,这一篇就整理出来。
二、理还乱?
本篇探讨话题的初始示例如下:
public static void main(String[] args) { new Thread(()->{ try { TimeUnit.SECONDS.sleep(10); for(int i =0;i<4;i++) { System.out.println(LocalDateTime.now() + " myThread exit"); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); //调用start 来 启动子线程 //主线程继续同时向下执行 System.out.printf(LocalDateTime.now() + " main thread exit"); } 复制代码
打印结果:
2022-12-11T13:17:47.939 main thread exit2022-12-11T13:17:57.877 myThread exit 2022-12-11T13:17:57.878 myThread exit 2022-12-11T13:17:57.878 myThread exit 2022-12-11T13:17:57.878 myThread exit Process finished with exit code 0 复制代码
- 问题:朋友问为啥主线程没有立即退出?
如果把子线程设置为deamon
Thread,则主线程退出后进程就退出了。
public static void main(String[] args) { Thread thread = new Thread(() -> { try { TimeUnit.SECONDS.sleep(10); for (int i = 0; i < 4; i++) { System.out.println(LocalDateTime.now() + " myThread exit"); } } catch (InterruptedException e) { e.printStackTrace(); } }); thread.setDaemon(true);//设置为 deamon thread.start(); //调用start 来 启动子线程 //主线程继续同时向下执行 System.out.printf(LocalDateTime.now() + " main thread exit"); } 复制代码
- 从结果看主线程是立即结束,进程快速退出了。
2022-12-11T13:32:58.154 main thread exit Process finished with exit code 0 复制代码
- 问题:为何如此?
看setDaemon(boolean on)
方法的注释可知,所有非 deamon 线程都退出时,进程才退出,也就是说运行中的 deamon 线程不能阻止进程的退出。
Marks this thread as either a daemon thread or a user thread. The Java Virtual Machine exits when the only threads running are all daemon threads.
- 问题:线程进入
sleep
暂定执行状态,干嘛非要被interrupt
啊?java.lang.Thread.sleep(xxx)
方法(注意是类方法),作用是使当前正在执行的线程暂停指定的时间,上述代码中,当子线程调用TimeUnit.SECONDS.sleep(10)
后,子线程会被暂停执行(放弃 CPU 时间片),等 10 秒后才醒来继续执行,如果希望线程快速醒来并退出,则可将这个子线程interrupt
(中断),之后子线程会快速感知到中断状态并抛出 InterruptedException 异常。
同学,认真思考的样子是这样嘛?
- 问题:线程中断是什么机制呢?
`java.lang.Thread#interrupt()`方法是实例级方法。当线程正在执行`wait()`、`sleep()`、`join()`方法时线程是处于【waitting】状态,可理解为内部仍会不断地检查中断状态(interrupt status)的值;`interrupt`方法会改变目标线程的中断状态(interrupt status);所以当被`interrupt()`后,则抛出`InterruptedException`异常。还有一个重点:::异常抛出后,线程内的中断状态会被重置为`false`,所以如果捕获到异常,之后要安排好退出线程的逻辑,否则就可能是bug。有两个方式判断中断状态: * Thread实例级方法`java.lang.Thread#isInterrupted()` 复制代码
- :用来检查指定线程的中断状态,true 为中断状态,false 为非中断状态。
* Thread实例级方法`java.lang.Thread#interrupted()` 复制代码
- :返回线程的中断状态,需特别注意还调用之后还将清除中断状态(置为 false)。
- 问题:
sleep
方法会释放 CPU 资源,那会把synchronized
锁释放掉嘛?
例如synchronized
可使用 对象实例锁 和 类锁这两种,线程中的sleep
只是放弃了 CPU 时间片的使用权,但并不会释放这种同步锁。
public class SynchronizedDemo { /** * 实例方法锁 即 this 实例锁 * @return */ public synchronized String 实例方法锁(){ return ""; } /** * 同 this 实例锁 * @return */ public String this锁(){ synchronized (this){ return ""; } } /** * 静态方法锁 即类锁 * @return */ public static synchronized String 静态方法锁(){ return ""; } /** * 类锁,同上 * @return */ public String 类方法锁(){ synchronized (SynchronizedDemo.class){ return ""; } } } 复制代码
三、新的思考
- 问题:前面除了
sleep()
,还提到了join()
,它是用来干嘛?
java.lang.Thread.join()
方法是用于辅助线程之间协作, 通过join()
方法等待目标线程终止。这里有两个关键点:一个是会等待,另一个是目标线程的终止。- 问题:为什么要等待目标线程终止呢?一般使用子线程有两种情况:
- 一种情况是把任务交给子线程去做,并不特别关心执行的结果
- 另外一种则是采用多线程并行的策略来提升计算效率,需要拿到各子线程计算的结果。
- 对于第一种情况,只要给了线程终止信号(如设置退出循环的信号、通过
interrupt
打断sleep
,快速结束线程),当线程内的任务逻辑结束后,线程后续自然终止了。而第二种情况是子线程计算结束后线程才处于终止状态,也即当子线程终止之后才可以取结果,而join()
方法则是等待置到子线程终止后才返回,而返回之后从子线程中取出的结果才是正确的。 - 问题:日更写 19 篇了不累嘛?
累、累、累,我先休息会儿了,多线程有好多有意思的知识点,咱们慢慢聊。
四、最后说一句
我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。