LockSupport与线程中断 | 字数总计: 3.4k | 阅读时长: 14分钟 | 阅读量: |
中断机制 首先一个线程不应该由其他线程来强制中断或停止 ,而是应该由线程自己自行停止。所以Thread.stop
,Thread.suspend
, Thread.resume
都已经被废弃了。
其次在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。中断只是一种协作机制 ,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt
方法,该方法也仅仅是将线程对象的中断标识设成true ;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断:该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
中断相关的API方法
方法
描述
public void interrupt()
实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
public static boolean interrupted()
静态方法,Thread.interrupted();
判断线程是否被中断,并清除当前中断状态这个方法做了两件事 :1. 返回当前线程的中断状态 2. 将当前线程的中断状态设为false
public boolean isInterrupted()
实例方法,判断当前线程是否被中断(通过检查中断标志位)
如何使用中断标识停止线程 在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑。
通过volatile变量实现 volatile保证了可见性,t2修改了标志位后能马上被t1看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Main { private static volatile boolean isStop = false ; public static void main (String[] args) { new Thread (() -> { while (true ) { if (isStop) { System.out.println(Thread.currentThread().getName() + "线程------isStop被修改为true,程序终止" ); break ; } System.out.println("-------hello interrupt" ); } }, "t1" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { isStop = true ; }, "t2" ).start(); } }
通过AtomicBoolean变量实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Main { private final static AtomicBoolean atomicBoolean = new AtomicBoolean (true ); public static void main (String[] args) { Thread t1 = new Thread (() -> { while (atomicBoolean.get()) { try { TimeUnit.MILLISECONDS.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 ------hello interrupt " ); } }, "t1" ); t1.start(); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } atomicBoolean.set(false ); } }
通过Thread类自带的中断API方法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Main { public static void main (String[] args) { Thread t1 = new Thread (() -> { while (true ) { if (Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为true,程序终止" ); break ; } System.out.println("t1 ------hello interrupt " ); } }, "t1" ); t1.start(); try { TimeUnit.MILLISECONDS.sleep(20 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { t1.interrupt(); }, "t2" ).start(); } }
中断协商案例深度解析 当对一个线程,调用 interrupt() 时:
如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行 ,不受影响 。所以, interrupt() 并不能真正地中断线程,需要被调用的线程自己进行配合才行。
如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
中断不活动的线程不会产生任何影响
当线程的中断标识为true,线程不会立刻停止(一直输出i到300):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Main { public static void main (String[] args) throws InterruptedException { Thread t1 = new Thread (() -> { for (int i = 0 ; i < 300 ; i++) { System.out.println("-------" + i); } System.out.println("after t1.interrupt()--第2次---: " + Thread.currentThread().isInterrupted()); }, "t1" ); t1.start(); System.out.println("before t1.interrupt()----: " + t1.isInterrupted()); t1.interrupt(); try { TimeUnit.MILLISECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("after t1.interrupt()--第1次---: " + t1.isInterrupted()); try { TimeUnit.MILLISECONDS.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("after t1.interrupt()--第3次---: " + t1.isInterrupted()); } }
运行结果:
before t1.interrupt()—-: false after t1.interrupt()–第1次—: true after t1.interrupt()–第2次—: true after t1.interrupt()–第3次—: false (这里false的原因应该是线程已经terminated标志位被重置了)
如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt
方法,那么线程将立即退出被阻塞状态 (中断状态将被清除 ),并抛出一个InterruptedException异常。
sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过thread.interrupt()方法再次将中断标志设置为true,这就导致无限循环了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Main { public static void main (String[] args) throws InterruptedException { Thread t1 = new Thread (() -> { while (true ) { if (Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "\t" + "中断标志位:" + Thread.currentThread().isInterrupted() + "程序终止" ); break ; } try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----hello InterruptDemo03" ); } }, "t1" ); t1.start(); try { TimeUnit.MILLISECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (t1::interrupt).start(); } }
静态方法Thread.interrupted() 静态方法,Thread.interrupted();
判断线程是否被中断,并清除当前中断状态这个方法做了两件事 :1. 返回当前线程的中断状态 2. 将当前线程的中断状态设为false
1 2 3 4 5 6 7 8 9 10 11 12 public class Main { public static void main (String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName() + "---" + Thread.interrupted()); System.out.println(Thread.currentThread().getName() + "---" + Thread.interrupted()); System.out.println("111111" ); Thread.currentThread().interrupt(); System.out.println("222222" ); System.out.println(Thread.currentThread().getName() + "---" + Thread.interrupted()); System.out.println(Thread.currentThread().getName() + "---" + Thread.interrupted()); } }
运行结果:
main—false main—false 111111 222222 main—true main—false
interrupted()
对比isInterrupted()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static boolean interrupted () { return currentThread().isInterrupted(true ); } public boolean isInterrupted () { return isInterrupted(false ); } private native boolean isInterrupted (boolean ClearInterrupted) ;
线程等待唤醒机制 3种让线程等待和唤醒的方法:
使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
LockSupport类的park()
和 unpark()
的作用分别是阻塞线程和解除阻塞线程
Object类中的wait和notify方法
wait和notify方法必须要在同步 块或者方法里面,且成对 出现使用
先wait后notify才OK
正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Main { public static void main (String[] args) { Object objectLock = new Object (); new Thread (() -> { synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t ---- come in" ); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了" ); }, "t1" ).start(); try { TimeUnit.SECONDS.sleep(3L ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "\t ---发出通知" ); } }, "t2" ).start(); } }
运行结果:
t1 —- come in t2 —发出通知 t1 —被唤醒了
异常1 — 去掉synchronized:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.util.concurrent.TimeUnit;public class Main { public static void main (String[] args) { Object objectLock = new Object (); new Thread (() -> { System.out.println(Thread.currentThread().getName() + "\t ---- come in" ); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了" ); }, "t1" ).start(); try { TimeUnit.SECONDS.sleep(3L ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "\t ---发出通知" ); }, "t2" ).start(); } }
运行结果:
t1 —- come in Exception in thread “t1” java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at Main.lambda$main$0(Main.java:11) at java.lang.Thread.run(Thread.java:748) Exception in thread “t2” java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method) at Main.lambda$main$1(Main.java:28) at java.lang.Thread.run(Thread.java:748)
异常2 — 把notify和wait的执行顺序对换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Main { public static void main (String[] args) { Object objectLock = new Object (); new Thread (() -> { try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t ---- come in" ); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了" ); }, "t1" ).start(); new Thread (() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "\t ---发出通知" ); } }, "t2" ).start(); } }
运行结果(程序一直在等待):
t2 —发出通知 t1 —- come in
Condition接口中的await和signal方法
Condition中的线程等待和唤醒方法,需要先获取锁
一定要先await后signal,不能反了
正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Main { public static void main (String[] args) { Lock lock = new ReentrantLock (); Condition condition = lock.newCondition(); new Thread (() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"\t-----come in" ); condition.await(); System.out.println(Thread.currentThread().getName()+"\t -----被唤醒" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"t1" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName()+"\t" +"我要进行唤醒" ); },"t2" ).start(); } }
运行结果:
t1 —–come in t2 我要进行唤醒 t1 —–被唤醒
异常与Object的类似:先唤醒后等待;缺少lock
LockSupport类中的park和unpark方法 Object和Condition使用的限制条件:
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1 。
正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Main { public static void main (String[] args) { Thread t1 = new Thread (() -> { System.out.println(Thread.currentThread().getName() + "\t----------come in" ); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" ); }, "t1" ); t1.start(); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (() -> { LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1" ); }, "t2" ).start(); } }
运行结果:
t1 ———-come in t2 —–发出通知,去唤醒t1 t1 ———-被唤醒了
之前错误的先唤醒后等待,LockSupport照样支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) { Thread t1 = new Thread (() -> { try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis()); }, "t1" ); t1.start(); new Thread (() -> { LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1" ); }, "t2" ).start(); } }
运行结果:
t2 —–发出通知,去唤醒t1 t1 ———-come in 1665242288963 t1 ———-被唤醒了 1665242288963
sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下。先执行了unpark(t1)导致上面的park方法形同虚设无效,时间是一样的 类似于高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。
许可的累加上限是1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Main { public static void main (String[] args) { Thread t1 = new Thread (() -> { try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis()); LockSupport.park(); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis()); }, "t1" ); t1.start(); new Thread (() -> { LockSupport.unpark(t1); LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1" ); }, "t2" ).start(); } }
运行结果(程序一直运行着):
t2 —–发出通知,去唤醒t1 t1 ———-come in 1665242531610
总结:
LockSupport是一个线程阻塞工具类, 所有的方法都是静态方法, 可以让线程在任意位置阻塞, 阻塞之后也有对应的唤醒方法。归根结底, LockSupport调用的Unsafe
中的native代码。
LockSupport提供park()
和unpark()
方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit) 关联。每个线程都有一个相关的permit, permit最多只有一个 , 重复调用unpark()
也不会积累凭证。