中断机制

首先一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以Thread.stopThread.suspendThread.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();//把t1中断
}, "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());
//实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
t1.interrupt();
//活动状态,t1线程还在执行中
try {
TimeUnit.MILLISECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after t1.interrupt()--第1次---: " + t1.isInterrupted());
//非活动状态,t1线程不在执行中,已经结束执行了。
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();
//Thread.currentThread().interrupt(); //假如加了这个,程序可以终止,只会抛异常
}
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);
}

/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
  • 它们在底层都调用了native方法isInterrupted,方法的注释也清晰的表达了“中断状态将会根据传入的ClearInterrupted参数值确定是否重置”。

  • 所以,静态方法interrupted将会清除中断状态(传入的参数ClearInterrupted为true),实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)。

线程等待唤醒机制

3种让线程等待和唤醒的方法:

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

  2. 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

  3. 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(() -> {
// 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
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()也不会积累凭证。