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个,累加无效。