3.LockSupport与线程中断
LockSupport与线程中断
线程中断
首先
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop,Thread.suspend, Thread.resume 都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制—中断,也即中断标识协商机制。
中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竞该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断:通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
中断机制以及java相关api
如何中断一个线程
- 通过volatile变量实现
static volatile boolean isStop = false; public static void main(String[] args) { new Thread(() -> { while (!isStop) { System.out.println("---------t1---------"); } System.out.println("---------t1 stop---------"); }, "t1").start(); new Thread(() -> { isStop = true; }, "t2").start(); } 结果 ---------t1--------- ---------t1--------- ---------t1 stop---------
- 使用原子变量 AtomicBoolean
static volatile AtomicBoolean atomicBoolean = new AtomicBoolean(false); public static void main(String[] args) { new Thread(() -> { while (!atomicBoolean.get()) { System.out.println("---------t1---------"); } System.out.println("---------t1 stop---------"); }, "t1").start(); new Thread(() -> { atomicBoolean.set(true); }, "t2").start(); } 结果: ---------t1--------- ---------t1 stop---------
- 使用Thread自带中断方法
Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("---------t1 stop---------"); break; } System.out.println("---------t1---------"); } }, "t1"); t1.start(); new Thread(t1::interrupt, "t2").start(); // 或者t1自己设置标志位 // t1.interrupt(); 执行结果 ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1--------- ---------t1 stop---------
中断三大方法说明:
public void interrupt()
实例方法,Just to set the interrupt flag
实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程 具体来说,当对一个线程,调用interrupt()时:
1、如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已:被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。
2、如果线程处于被阻塞状态(例如处于sleep, wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
这么使用可能会导致程序死循环
Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("---------t1 stop---------"); break; } try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("---------t1---------"); } }, "t1"); t1.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(t1::interrupt, "t2").start(); 执行流程: 1、正常执行打印 ---------t1--------- 中断标志位 = false 2、t2 设置中断标志位 中断标志位 = true 3、t1遇到 Thread.sleep(200); 方法 抛出 InterruptedException 并且设置清除中断标志位 中断标志位 = false 4、最终导致程序无限循环 解决: 在sleep() catch 代码块中 重新设置中断标志位 为 true public static boolean interrupted()
静态方法,Thread.interrupted();判断线程是否被中断并清除当前中断状态。 这个方法做了两件事:
1. 返回当前线程的中断状态,测试当前线程是否已被中断
2. 将当前线程的中断状态清零并重新设为false,清除线程的中断状态
此方法有点不好理解,如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次 的结果可能不一样 public boolean isInterrupted()
实例方法
判断当前线程是否被中断〔通过检查中断标志位)
LockSupport
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park()和 unpark()的作用分别是阻塞线程和解除阻塞线程
三种线程等待和幻唤醒方法
- 使用object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程方式
// 1、wait()方法和 notify()方法 只能用在 synchronized 同步代码块中 // object 的线程等待和唤醒方法 Object o = new Object(); new Thread(() -> { System.out.println("++++++++++t1开始++++++++++"); try { o.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "t1").start(); new Thread(() -> { o.notify(); System.out.println("+++++++++++t2开启唤醒+++++++"); }, "t2").start(); 结果: ++++++++++t1开始++++++++++ Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at LockSupportDemo.lambda$main$0(LockSupportDemo.java:32) at java.lang.Thread.run(Thread.java:748) java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method) at LockSupportDemo.lambda$main$1(LockSupportDemo.java:41) at java.lang.Thread.run(Thread.java:748) // 2、notify在wait方法前面先执行,程序无法唤醒 // object 的线程等待和唤醒方法 Object o = new Object(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (o) { System.out.println("++++++++++t1开始++++++++++"); try { o.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("+++++++++++t1 被唤醒+++++++++++"); } }, "t1").start(); new Thread(() -> { synchronized (o) { o.notify(); System.out.println("+++++++++++t2开启唤醒+++++++"); } }, "t2").start(); 结果: +++++++++++t2开启唤醒+++++++ ++++++++++t1开始++++++++++ (程序没有正常退出)
- 使用Juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程方式
// condition类的await signal方法 ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { lock.lock(); System.out.println("++++++++++t1开始++++++++++"); try { condition.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } System.out.println("+++++++++++t1 被唤醒+++++++++++"); }, "t1").start(); new Thread(() -> { lock.lock(); condition.signal(); System.out.println("+++++++++++t2开启唤醒+++++++"); lock.unlock(); }, "t2").start(); 结果: ++++++++++t1开始++++++++++ +++++++++++t2开启唤醒+++++++ +++++++++++t1 被唤醒+++++++++++ await signal 会出现与wait notify相同的问题
-
LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),但与Semaphore不同的是,许可的累加上限是1。 park()阻塞 unpark() 唤醒
park 源码 public static void park() { UNSAFE.park(false, 0L); } public native void park(boolean var1, long var2); // boolean var1 =》 boolean isAbsolute() 是否拥有许可证 默认false =》 没有通行证 // long var2 =》 long time 持续时间 默认 0l =》 不放行 // permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程的发放permit,park方法才会被唤醒。 unpark() 源码 public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); } // 要发许可证的线程 Thread thread // 正常执行 Thread t1 = new Thread(() -> { System.out.println("++++++++++t1开始++++++++++"); LockSupport.park(); System.out.println("+++++++++++t1 被唤醒+++++++++++"); }, "t1"); t1.start(); new Thread(() -> { LockSupport.unpark(t1); System.out.println("+++++++++++t2开启唤醒+++++++"); }, "t2").start(); 结果 ++++++++++t1开始++++++++++ +++++++++++t2开启唤醒+++++++ +++++++++++t1 被唤醒+++++++++++ // 先执行 unpark 再执行park Thread t1 = new Thread(() -> { System.out.println("++++++++++t1开始++++++++++"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new RuntimeException(e); } LockSupport.park(); System.out.println("+++++++++++t1 被唤醒+++++++++++"); }, "t1"); t1.start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new RuntimeException(e); } LockSupport.unpark(t1); System.out.println("+++++++++++t2开启唤醒+++++++"); }, "t2").start(); 结果 ++++++++++t1开始++++++++++ +++++++++++t2开启唤醒+++++++ +++++++++++t1 被唤醒+++++++++++ 程序完整执行----》先发许可证而后 t1线程持证上岗,不会阻塞 // 发多次通行证 Thread t1 = new Thread(() -> { System.out.println("++++++++++t1开始++++++++++"); LockSupport.park(); LockSupport.park(); System.out.println("+++++++++++t1 被唤醒+++++++++++"); }, "t1"); t1.start(); new Thread(() -> { // 多次发许可 LockSupport.unpark(t1); LockSupport.unpark(t1); LockSupport.unpark(t1); System.out.println("+++++++++++t2开启唤醒+++++++"); }, "t2").start(); // 运行结果 ++++++++++t1开始++++++++++ +++++++++++t2开启唤醒+++++++ (程序没有停止运行) 总结
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport是一个线程阻寨工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程 LockSupport和每个使用它的线程都有一个许可(permit)关联。
每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
也就是说
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时
如果有凭证,则会直接消耗掉这个凭证然后正常退出;*如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)