Java线程interrupt方法详解

后端 潘老师 3个月前 (11-25) 69 ℃ (0) 扫码查看

本文主要讲解Java线程interrupt方法相关内容,我们一起来学习一下吧!

虽然有很多同学学过了java线程相关的知识,但是对interrupt始终不能深入理解,我们将会通过案例来实操讲解下interrupt的作用。

1、打断线程阻塞状态

interrupt可以打断线程的阻塞状态,在线程调用sleep、wait和join方法进入阻塞状态时,我们可以通过调用线程的interrupt方法来将其打断,线程将会被唤醒并抛出InterruptedException中断异常,我们看下下面的代码:

@Slf4j
public class ThreadInterrupt {
    @Test
    public void testInterrupt(){
        // 创建t1线程
        Thread t1 = new Thread(()->{
            log.debug("t1 start");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                log.debug("t1 interrupt");
                e.printStackTrace();
            }
            log.debug("t1 over");
        },"t1");
        // 启动
        t1.start();
        // 主线程休眠1s保证t1进入sleep状态
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断
        t1.interrupt();
        // 确保中断结束
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 中断标记:{}", t1.isInterrupted());
    }
}

运行输出如下:

我们发现中断最后中断标记为false,是因为interrupt在打断sleep、wait和join方法后会将中断标记重新置为false,如果你在上述代码中,如果删除打印中断标记前的sleep,有机会在抛出中断异常之前打印出中断标记为true的情况,因为interrupt方法有如下两个特点:

  • Thread.interrupt()方法用于中断一个线程。当调用此方法时,将设置线程的中断标志位为true。
  • 如果目标线程正在被阻塞(如在sleep、wait或join方法中)或者正在等待获取一个内置锁(如synchronized块或方法),那么它将抛出一个InterruptedException异常,并清除线程的中断状态,也就是会将中断标志为置为false。

2.打断正常线程

interrupt方法除了可以打断阻塞的线程,还可以打断结束正常的线程,而且方式还比较优雅,我们先看以下代码:

public static void main(String[] args) {
        // 创建t1线程
        Thread t1 = new Thread(()->{
            while (true) {
                System.out.println("running");
            }
        },"t1");
        // 启动
        t1.start();
        // 主线程休眠1s保证t1运行起来
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断
        t1.interrupt();
    }

你会发现t1线程会陷入死循环打印,即使interrupt被调用,也不会停止运行,因此想要停止线程,我们需要判断中断标志位,来退出循环结束线程:

public static void main(String[] args) {
        // 创建t1线程
        Thread t1 = new Thread(()->{
            while (true) {
                // 判断打断标志位,若为true退出循环
                boolean isInterrupted = Thread.currentThread().isInterrupted();
                if(isInterrupted) {
                    break;
                }
                System.out.println("running");
            }
        },"t1");
        // 启动
        t1.start();
        // 主线程休眠1s保证t1运行起来
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断
        t1.interrupt();
    }

这样我们可以优雅地结束线程的运行了。

3.(终止)模式之两阶段终止

两阶段终止:英文称为Two Phase Termination

即:在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。

3.1. 错误思路

● 使用线程对象的 stop() 方法停止线程
○ stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
● 使用 System.exit(int) 方法停止线程
○ 目的仅是停止一个线程,但这种做法会让整个程序都停止

3.2. 两阶段终止模式

我们先创建一个监控线程类:

@Slf4j
public class TwoPhaseTermination {

    /**
     * 监控线程
     */
    private Thread monitor;

    /**
     * 启动监控线程
     */
    public void start(){
        monitor = new Thread(()->{
            while (true) {
                Thread current = Thread.currentThread();
                if(current.isInterrupted()) {
                    log.debug("料理后事...");
                    break;// 结束线程
                }
                // 执行监控-如果在正常运行中被中断,中断标记会置为true退出循环
                log.debug("执行监控");
                // 睡眠1s-如果在sleep时被interrupt会在抛出异常后清除中断标记,即恢复为false,导致无法结束线程,因此需要在catch中再次中断
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 将中断标记置为true
                    current.interrupt();
                }
            }
        });
        monitor.start();
    }

    /**
     * 停止监控线程
     */
    public void stop(){
        monitor.interrupt();
    }

}

再写个测试方法:

@Test
public void testTwoPhaseTermination() throws InterruptedException {
    TwoPhaseTermination t1 = new TwoPhaseTermination();
    t1.start();
    Thread.sleep(4500);
    t1.stop();
}

运行结果:

由运行记录可见,该监控线程这次正好在sleep时被中断了,然后料理线程即将结束的后事,最后线程停止运行。

4.interrupted()方法

interrupted()方法也是用来判断线程是否被打断,与isInterrupted()方法不同的是isInterrupted()判断完后不会清除中断标记,而interrupted()判断完后会清除中断标记。

5.interrupt打断park线程

打断 park 线程, 不会清空打断状态,我们写个代码案例:

public static void main(String[] args) throws InterruptedException {
    testInterruptPark();
}

public static void testInterruptPark() throws InterruptedException {
    Thread t1 = new Thread(()->{
        log.debug("park");
        LockSupport.park();
        log.debug("unpark");
        log.debug("中断标志:{}", Thread.currentThread().isInterrupted());
    },"t1");

    t1.start();

    Thread.sleep(1000);
    t1.interrupt();
}

运行结果:

注意:

1)如果不执行t1.interrupt方法,只会打印park,LockSupport.park()让程序阻塞,无法继续运行

2)t1.interrupt执行中断后,中断标记没有被清除,仍然为true,如果后续再次执行LockSupport.park()则不会再阻塞程序,而是直接继续往下运行,也就是park方法失效了,如下:

log.debug("park");
LockSupport.park();
log.debug("unpark");
log.debug("中断标志:{}", Thread.currentThread().isInterrupted());
LockSupport.park();
log.debug("park此时无效,继续执行");

如果想要park继续生效,就要恢复中断标记,而恢复就需要执行interrupted()方法,如下:

log.debug("park");
LockSupport.park();
log.debug("unpark");
log.debug("中断标志:{}", Thread.currentThread().interrupted());
LockSupport.park();
log.debug("park此时有效,程序阻塞");

6. 总结

以上就是Java线程interrupt方法详解的全部内容,希望对你有帮助。欢迎持续关注潘子夜个人博客(www.panziye.com),学习愉快哦!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/11844.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】