1、线程中断机制
1.1、面试题
1.2、什么是中断机制
首先
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,
Thread.stop
,Thread.suspend
,Thread.resume
都已经被废弃了。
其次
在Java
中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java
提供了一种用于停止线程的协商
机制——中断,也即中断标识协商机制。
中断只是一种协作协商机制,Java
没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt
方法,该方法也仅仅是将线程对象的中断标识设成true
;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true
,表示别的线程请求这条线程中断,
此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true
表示中断,为false
表示未中断;
通过调用线程对象的interrupt
方法将该线程的标识位设为true
;
可以在别的线程中调用,也可以在自己的线程中调用。
1.2.1、3个方法 !!!
public void interrupt() | 实例方法,Just to set the interrupt fLag 实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程 |
public static boolean interrupted() | 静态方法,Thread.interrupted();判断线程是否被中断并清除当前中断状态。 这个方法做了两件事: 1返回当前线程的中断状态,测试当前线程是否已被中断 2将当前线程的中断状态清零并重新设为false,清除线程的中断状态 此方法有点不好理解,如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次的结果可能不一样 |
public boolean isInterrupted() | 实例方法,判断当前线程是否被中断(通过检查中断标志位) |
1.3、中断机制考点
1.3.1、如何停止中断运行中的线程
1.3.1.1、volatile变量实现
public class InterruptDemo {
static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止");
break;
}
System.out.println("--------hello volatile");
}
}, "t1").start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
isStop = true;
}, "t2").start();
}
}
1.3.1.2、AtomicBoolean
public class InterruptDemo {
static volatile boolean isStop = false;
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为true,程序停止");
break;
}
System.out.println("--------hello atomicBoolean");
}
}, "t1").start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
atomicBoolean.set(true);
}, "t2").start();
}
}
1.3.1.3、Thread类自带中断api实例方法实现
在需要中断的线程中不断监听中断状态,
一旦发生中断,就执行相应的中断处理业务逻辑stop线程
api
public class InterruptDemo {
static volatile boolean isStop = false;
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
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("--------hello isInterrupted api");
}
}, "t1");
t1.start();
System.out.println("---------t1的默认中断标志位:" + t1.isInterrupted()); //false,后面设置了就是true
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
/* new Thread(() -> {
t1.interrupt();
}, "t2").start();*/
t1.interrupt();
}
}
1.3.2、实例方法interrupt(),源码分析
1.3.3、实例方法isInterrupted()源码分析
1.3.4、当前线程的中断标识为true,是不是线程就立刻停止?
具体来说,当对一个线程,调用interrupt()
时:
①如果线程处于正常活动状态,那么会将该线程的中断标志设置为true
,仅此而已**。
被设置中断标志的线程将继续正常运行,不受影响。
所以,interrupt()
并不能真正的中断线程,需要被调用的线程自己进行配合才行。
②如果线程处于被阻塞状态(例如处于sleep, wait, join
等状态),在别的线程中调用当前线程对象的interrupt
方法,
那么线程将立即退出被阻塞状态,并抛出一个InterruptedException
异常。
public class InterruptDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println("-----" + i);
}
System.out.println("t1线程调用t1.interrupt()后的的中断标识02: " + Thread.currentThread().isInterrupted()); //false
}, "t1");
t1.start();
System.out.println("t1线程默认的终端标识: " + t1.isInterrupted()); //false
try {
TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();//true
System.out.println("t1线程调用t1.interrupt()后的的中断标识: " + t1.isInterrupted()); //true
}
}
发现 03 是false
因为t1
已经结束了
- 中断不活动的线程不会产生任何影响
public class InterruptDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println("-----" + i);
}
System.out.println("t1线程调用t1.interrupt()后的的中断标识02: " + Thread.currentThread().isInterrupted()); //false
}, "t1");
t1.start();
System.out.println("t1线程默认的终端标识: " + t1.isInterrupted()); //false
try {
TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();//true
System.out.println("t1线程调用t1.interrupt()后的的中断标识01: " + t1.isInterrupted()); //true
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1线程调用t1.interrupt()后的的中断标识03: " + t1.isInterrupted()); //false
}
}
②如果线程处于被阻塞状态(例如处于sleep
, wait
, join
等状态),在别的线程中调用当前线程对象的interrupt方法,
那么线程将立即退出被阻塞状态,并抛出一个InterruptedException
异常。
发现抛了异常之后,就进入死循环了
public class InterruptDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "\t 中断标志位 : "+Thread.currentThread().isInterrupted()+"程序停止");
break;
}
System.out.println("----hello InterruptDemo3");
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
t1.interrupt();
},"t2").start();
}
}
解决方案
public class InterruptDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "\t 中断标志位 : "+Thread.currentThread().isInterrupted()+"程序停止");
break;
}
System.out.println("----hello InterruptDemo3");
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //再次设为true
e.printStackTrace();
}
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
t1.interrupt();
},"t2").start();
}
}
/**
* 1、中断标志位,默认false
* 2、t2 -----> t1 发送了中断协商, t2调用 t1.interrupt(); 中断标志位true
* 3、中断标志位true,正常情况,程序停止,
* 4、中断标志位true,异常情况 InterruptedException,就会把中断状态将被清除,并且将收到InterruptedException。
* 中断标志位false 导致 无限循环
* 5、再catch中,需要再次给中断标志位设置为true,2次调用停止程序才ok
*/
为什么要在异常处,再调用一次?
如果该特程泪塞的调用wait()
, wait(long)
,或wait(long,int)
的方法Object
类,或的join()
,join(1ong)
, join(long , int)
,
sleep(long)
,或 sleep(long . int)
这个类的方法,那么它的中断状态将被清除,并且将收到InterruptedException
。
1.3.4.1、总结
sleep
方法抛出InterruptedException
后,中断标识也被清空置为false
,
我们在catch
没有通过调用th.interrupt()
方法再次将中断标识置为true
.这就导致无限循环了
1.3.5、静态方法Thread.interrupted(),谈谈你的理解
public class InterruptDemo4 {
public static void main(String[] args) {
//测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,
//第二次再调用时中断状态已经被清除,将返回一个false。
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); //false
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); //false
System.out.println("----1");
Thread.currentThread().interrupt();// 中断标志位设置为true
System.out.println("----2");
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); //true,先返回true,再重新设置为false
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); //false
}
}
2、LockSupport是什么
用于创建锁和其他同步类的基本线程阻塞原语
LockSupport
中的park()
和 unpark()
的作用分别是阻塞线程和解除阻塞线程
3、线程等待唤醒机制
3.1、3种让线程等待和唤醒的方法
方式1:
- 使用
Object
中的wait()
方法让线程等待,使用Object
中的notify()
方法唤醒线程
方式2:
- 使用
JUC
包中Condition的await()
方法让线程等待,使用signal()
方法唤醒线程
方式3:
LockSupport
类可以阻塞当前线程以及唤醒指定被阻塞的线程
3.2、Object类中的wait和notify方法实现线程等待和唤醒
2.1、正常
public class LockSupportDemo {
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(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}
},"t2").start();
}
}
3.2.2、异常1
public class LockSupportDemo {
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(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
// synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
// }
},"t2").start();
}
}
3.2.3、异常2
将notify
放在wait
方法前面
程序无法执行,无法唤醒
public class LockSupportDemo {
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();
/*
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(()->{
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}
},"t2").start();
}
}
发现t1
一直阻塞
3.2.4、总结
wait
和notify
方法必须要在同步块或者方法里面,且成对出现使用
先wait
后notify
才OK
3.3、Condition接口中的await后signal方法实现线程的等待和唤醒
3.3.1、正常
public class LockSupportDemo {
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();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}finally {
lock.unlock();
}
},"t2").start();
}
}
3.3.2、异常1
注释掉加锁和解锁
public class LockSupportDemo {
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();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}finally {
// lock.unlock();
}
},"t2").start();
}
3.3.3、异常2
先signal
后await
public class LockSupportDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
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();
new Thread(()->{
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}finally {
lock.unlock();
}
},"t2").start();
}
.3.3.4、总结
Condtion
中的线程等待和唤醒方法,需要先获取锁
一定要先await
后signal
,不要反了
更上面的一样
3.4、LockSupport类中的park等待和unpark唤醒
3.4.1、是什么
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
3.4.2、主要方法
API
阻塞
park()/park(Object blocker)
唤醒
unpark(Thread thread)
3.4.2、代码
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
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(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(()->{
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
},"t2").start();
}
}
之前错误的先唤醒后等待,LockSupport
照样支持