线程中断与等待唤醒机制
什么是中断
首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,所以 Thread.stop、Thread.suspend、Thread.resume 都已经被废弃了
其次在 java 中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作,因此 java 提供了一种用于停止线程的机制-中断
中断只是一种协作机制,java 没有给中断增加任何语法,中断的过程完全需要自己实现
若要中断一个线程,需要手动调用该线程的 interrupt 方法,该方法也仅仅是将线程对象的中断标识设成 true,接着需要写代码不断地检测当前线程的标识位,如果为 true,表示别的线程要求这条线程中断,此时究竟该做什么,需要你自己写代码实现
每个线程对象中都有一个标识,用于表示线程是否被中断,该标识位为 true 表示中断,为 false 表示未中断,通过调用线程对象的 interrupt 方法将该线程的标识位设为 true,可以在别的线程中调用,也可以在自己的线程中调用
如何停止、中断一个运行中的线程
使用 volatile
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
| public class Test {
private static volatile boolean isStop = false;
public static void main(String[] args) { new Thread(() -> { while (true) { if (isStop) { System.out.println("isStop = true,程序结束。"); break; } System.out.println("hello isStop"); } }, "t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { isStop = true; }, "t2").start(); } }
|
使用 atomic
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
| public class Test {
private static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) { new Thread(() -> { while (true) { if (atomicBoolean.get()) { System.out.println("atomicBoolean = true,程序结束"); break; } System.out.println("hello atomic"); } }, "t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { atomicBoolean.set(true); }, "t2").start(); } }
|
使用 interrupt
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
| public class Test {
public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("isInterrupted = true,程序结束"); break; } System.out.println("hello interrupt"); } }, "t1"); t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { t1.interrupt(); }, "t2").start(); } }
|
中断方法介绍及说明
方法 |
描述 |
void interrupt() |
设置线程的中断标志为 true,不会停止线程 |
static boolean interrupted() |
判断线程是否被中断,并清除当前中断状态,这个方法做了两件事,1返回当前线程的中断状态,2将当前线程的中断状态设置为 false |
boolean isInterrupted() |
通过检查中断标志位判断当前线程是否被中断,它与 interrupted 都是调用了 native boolean isInterrupted(boolean ClearInterrupted),只不过它传入了 false,interrupted 传入了 true |
具体的来说,当对一个线程调用 interrupt() 时:
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已,被设置中断标志的线程将继续正常运行不受影响,所以 interrupt 并不能真正的中断线程,需要被调用的线程自己进行配合才行
- 如果线程处于被阻塞状态(例如处于 sleep、wait、join 等状态) ,在别的线程中调用当前线程对象的 interrpt 方法,那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException 异常
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
| public class Test {
public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("isInterrupted = true,程序结束"); break; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello Interrupt"); } }, "t1"); t1.start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { t1.interrupt(); }, "t2").start(); } }
|
等待唤醒方法
Object 的 wait 和 notify
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
| public class ObjectDemo {
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> { synchronized (objectLock) { try { System.out.println(Thread.currentThread().getName() + "启动"); objectLock.wait(); System.out.println(Thread.currentThread().getName() + "被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start();
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { synchronized (objectLock) { objectLock.notify(); } System.out.println(Thread.currentThread().getName() + "通知了"); }, "t2").start(); } }
|
wait 和 notify 方法必须要在同步块或者方法里面,且成对出现使用
先 wait 后 notify 才能正常使用
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 ConditionDemo {
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() + "启动"); condition.await(); System.out.println(Thread.currentThread().getName() + "被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t1").start();
try { TimeUnit.SECONDS.sleep(3L); } 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() + "通知了"); }, "t2").start(); } }
|
Condtion 中的线程等待和唤醒方法之前,需要先获取锁
先 await 后 signal 才能使用
LockSupport 的 park 和 unpark
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语
LockSupport 使用了一种名为 Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个 permit,permit 只有两个值 1 和 0,默认 0
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "启动"); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "被唤醒"); }, "t1"); t1.start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "通知了"); } }
|
park 和 unpark 没有先后顺序,但必须成对出现
阻塞
调用 LockSupport.park() 时
1 2 3
| public static void park() { UNSAFE.park(false, 0L); }
|
permit 默认是 0,所以一开始调用 park 方法,当前线程就会阻塞,直到别的线程将当前线程的 permit 设置为 1 时,park 方法会被唤醒,
然后会将 permit 再次设置为 0 并返回
唤醒
1 2 3 4
| public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
|
调用 unpark(thread) 方法后,就会将 thread 线程的许可 permit 设置成 1(注意多次调用 unpark 方法,不会累加,permit 值还是 1)会自动唤醒 thread 线程,即之前阻塞中的 LockSupport.park() 方法会立即返回