推荐一个java届大神:孙哥
孙哥主页
线程中断机制
从阿里蚂蚁金服面试题讲起
这三个方法区别?
如何中断一个运行中的线程??
如何停止一个运行中的线程??
什么是中断机制
首先
一个个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。
所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了.
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此,Java提供了一种用于停止线程的协商机制一一中断,也即中断标识协商机制。
中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true:
接着你需要自己写代码不断地检测当前线程的标识位,如果为tue,表示别的线程请求这条线程中断,
此时究竞该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为tue表示中断,为false表示未中断:
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
:::info 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现 每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为tue表示中断,为false表示未中断: 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。 :::
中断的三个API
- public void interrupt()
仅仅设置线程的中断状态为true ,发起一个协商而不会立刻停止线程 2如果线程处于被阻塞状态(例如处于sleep,wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
- public static boolean interrupted()
判断线程是否被中断并清除当前中断状态。 这个方法做了两件事: 1返回当前线程的中断状态,测试当前线程是否已被中断 2将当前线程的中断状态清零并重新设为fase,清除线程的中断状态
- public boolean isInterrupted()
判断当前线程是否被中断(通过检查中断标志位)
大厂面试题中断机制考点
如何停止中断运行中的线程?
- 通过volatile变量实现
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程t1
new Thread(()->{
while (true){
if(isStop){
System.out.println("停止");
break;
}
System.out.println("t1----hello volatile");
}
},"t1").start();
// 线程休眠10秒
TimeUnit.SECONDS.sleep(10);
// 创建并启动线程t2
new Thread(()->{
// 将isStop标记设置为true
isStop=true;
},"t2").start();
}
- 通过AtomicBoolean
/**
* 主线程入口
*
* @param args 参数数组
* @throws InterruptedException 线程中断异常
*/
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程t1
new Thread(()->{
while (true){
if(isStop.get()){
System.out.println("停止");
break;
}
System.out.println("t1----hello volatile");
}
},"t1").start();
// 线程休眠10秒
TimeUnit.SECONDS.sleep(5);
// 创建并启动线程t2
new Thread(()->{
// 将isStop标记设置为true
isStop.set(true);
},"t2").start();
}
- 通过Thread类自带的中断api实现
/**
* 主线程入口
*
* @param args 参数数组
* @throws InterruptedException 线程中断异常
*/
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程t1
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("停止");
break;
}
System.out.println("t1----hello interrupt api");
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(5);
// 创建并启动线程t2
new Thread(()->{
t1.interrupt();
},"t2").start();
}
当前线程的中断标识被设置为true的化,是不是线程立刻停止?
只会设置中断标志位,程序线程会继续运行
public class InterruptDemo3 {
public static void main(String[] args) {
Thread r1=new Thread(()->{
for (int i = 0; i < 300; i++) {
System.out.println("-------------"+i);
}
},"t1");
r1.start();
System.out.println("t1线程默认的中断标识:"+r1.isInterrupted());
r1.interrupt();
System.out.println("ti线程调用interrupt后的中断标识:"+r1.isInterrupted());
}
}
由此可见,调用interrupt后 标识变为true,线程并没有停止
当程序中调用sleep,wait等方法的情况
如果该线程阳毫的调用wait(),wait(long),或wait(Long,int)的方法0 bject:类,或的join(),join(Long),join(Long,int),sleep(long),或sleep(Long,int),这个类的方法,那么它的中断状态将被清除,并且将收到InterruptedException。
public static void main(String[] args) throws InterruptedException {
Thread r1=new Thread(()->{
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"线程被中断");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----hello");
}
},"t1");
r1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
r1.interrupt();
},"t2").start();
}
由于调用阻塞方法sleep ()
清除了中断状态
线程会一直执行下去
public class InterruptDemo4 {
public static void main(String[] args) throws InterruptedException {
Thread r1=new Thread(()->{
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"线程被中断");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//为什么要在异常处,再调用一次?
e.printStackTrace();
}
System.out.println("----hello");
}
},"t1");
r1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
r1.interrupt();
},"t2").start();
}
}
所以要在sleep方法 抛出异常的时候,重新给中断标志位赋值
Thread.interrupted()
判断线程是否被中断并清除当前中断状态。 这个方法做了两件事: 1返回当前线程的中断状态,例试当前线程是否已被中断 2将当前线程的中断状态清零并重新设为ase,清除线程的中断状态 此方法有点不好理解,如果连续两次调用此方法,则第二次调用将返回as,因为连续调用两次的结果可能不一样
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() +Thread.interrupted());
System.out.println(Thread.currentThread().getName() +Thread.interrupted());
System.out.println("-----1-----------");
Thread.currentThread().interrupt();
System.out.println("----2");
System.out.println(Thread.currentThread().getName() + " is"+Thread.interrupted());
System.out.println(Thread.currentThread().getName() + " is"+Thread.interrupted());
}
mainfalse
mainfalse
-----1-----------
-----2
main istrue
main isfalse
LockSupport
三种让线程等待和唤醒的方法
- wait notify
public class LockSupportDemo1 {
public static void main(String[] args) throws InterruptedException {
Object objectLock = new Object();
new Thread(()->{
synchronized(objectLock){
System.out.println(Thread.currentThread().getName() + " 进入同步代码块");
try {
objectLock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "被唤醒");
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
synchronized(objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName() + " 唤醒");
}
},"t2").start();
}
}
t1 进入同步代码块
t2 唤醒
t1被唤醒
如果先notify 再 wait 还能否唤醒??
public static void main(String[] args) throws InterruptedException {
Object objectLock = new Object();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(objectLock){
System.out.println(Thread.currentThread().getName() + " 进入同步代码块");
try {
objectLock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "被唤醒");
}
},"t1").start();
// TimeUnit.SECONDS.sleep(1);
new Thread(()->{
synchronized(objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName() + " 唤醒");
}
},"t2").start();
}
}
t1无法被唤醒
- conditon await()和 signal()方法
public class LockSupportDemo2 {
public static void main(String[] args) throws InterruptedException {
Object objectLock = new Object();
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lock.lock();
System.out.println(Thread.currentThread().getName() + " 进入同步代码块");
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "被唤醒");
lock.unlock();
},"t1").start();
// TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.lock();
condition.signal();
System.out.println(Thread.currentThread().getName() + " 唤醒");
lock.unlock();
},"t2").start();
}
}
同样不能唤醒t1
- LockSupport 类中的park等待和 unpark唤醒
public class LockSupportDemo3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 进入同步代码块");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "被唤醒");
}, "t1");
t1.start();
// TimeUnit.SECONDS.sleep(1);
new Thread(()->{
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + " 唤醒");
},"t2").start();
}
}
特点:
正常+无锁块要求
现在支持先唤醒,后等待
注意:许可证只能有一个
唤醒不了了
:::info LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程 LockSupport和每个使用它的线程都有一个许可(permit)关联。 每个线程都有一个相关的permit,permiti最多只有一个,重复调用unpark也不会积累凭证。 形象的理解 线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。 当调用par水方法时 *如果有凭证,则会直接消耗掉这个凭证然后正常退出: *如果无凭证,就必须阻塞等待凭证可用: 而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。 :::
为什么可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。 先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证: 而调用两次pa却需要消费两个凭证,证不够,不能放行。