线程中断与等待唤醒机制

线程中断与等待唤醒机制

什么是中断

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,所以 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 线程的中断标志位为 true
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) {
// 抛出异常后,中断标识也被清空置为 false,会导致死循环无法停下,所以需要再次调用 interrupt() 将中断标志设置为 true
// Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println("hello Interrupt");
}
}, "t1");
t1.start();

try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}

new Thread(() -> {
// 修改 t1 线程的中断标志位为 true
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() 方法会立即返回