当前位置: 首页 > 科技观察

再说说Java中的中断机制

时间:2023-03-20 18:22:11 科技观察

在Java中,用来终止一个正在运行的线程。它没有调用stop方法,而是自己设置一个flag,在安全点检测flag,决定是否退出,但也有可能是因为线程被挂起,线程无法走到flag。因此Java线程提供了中断机制,Thread类提供了中断线程执行的调用方法:interrupt,用于中断线程挂起的等待。调用中断方法后,线程会被唤醒,下一次CPU调度会继续执行被中断的代码。我们经常会调用Thread#sleep、Object#wait、Queue#poll等方法,要求我们处理InterruptedException异常。那么,抛出InterruptedException后线程会终止吗?如果没有捕获到InterruptedException,那么线程就会因为异常而终止,因为它异常终止,而不是因为它被中断了。如果捕获到InterruptedException,则线程不会终止。中断实际上只是jvm用来唤醒因锁竞争、I/O操作、睡眠等待而挂起的线程,并设置一个中断标志。我们可以使用这个标志来做一些处理。例如,当我们向远程服务器发送消息并休眠等待结果时,如果线程被唤醒并设置了中断标志,此时我们可以知道它不是在等待结果被唤醒起来,但它被中断唤醒。继续等待结果,或者放弃等待。xxl-job提供了任务取消操作,任何运行的线程只能使用中断机制结束线程任务,所以我们希望任务支持取消,所以写定时任务的时候要考虑清楚是否应该CatchInterruptedException,如何使用中断标志来结束任务,否则任务不会被取消。我们来看一个案例:@Testpublicvoidtest(){ExecutorServiceexecutorService=Executors.newSingleThreadExecutor();Futurefuture=executorService.submit(()->{while(true){System.out.println("rung.....");ThreadUtils.sleep(1000);}});ThreadUtils.sleep(1000);future.cancel(true);try{future.get();}catch(InterruptedException|CancellationException|ExecutionExceptione){e.printStackTrace();}ThreadUtils.sleep(1000*60);}本例创建一个只有一个线程的线程池,提交一个死序列任务,只调用ThreadUtils.sleep方法进入睡眠。通常我们调用Thread.sleep方法询问是否捕获中断异常。很多时候我们会嫌麻烦,所以用一个工具类提供sleep方法,然后捕获中断异常,比如ThreadUtils:publicclassThreadUtils{publicstaticvoidsleep(longmillis){try{Thread.sleep(millis);}catch(InterruptedExceptionignored){}}}在这种情况下,由于我们捕获了中断异常,这将导致任务不会终止,但是当我们调用future的get方法时会抛出CancellationException,如下所示。任务还在运行中……所以,在实际开发中,如果我们开发的Job也是如此,就会导致Job无法中断取消,直到Job完成或者重启。在开发作业时,应该合理考虑是否捕获中断异常。如果我们想让case中的任务终止,我们可以这样处理:@Testpublicvoidtest(){ExecutorServiceexecutorService=Executors.newSingleThreadExecutor();Futurefuture=executorService.submit(()->{while(true){System.out.println("rung....");try{Thread.sleep(1000);}catch(InterruptedExceptionex){System.err.println("interrupted");return;//退出无限循环}}});ThreadUtils.sleep(1000);future.cancel(true);try{future.get();}catch(InterruptedException|CancellationException|ExecutionExceptione){e.printStackTrace();}ThreadUtils.sleep(1000*60);}关于Thread的中断方法,注释描述的大致含义如下:如果被中断的线程当前正在调用Object#wait、Thread#join、Thread#sleep方法,就会收到InterruptedException,而中断标志将被清除;线程阻塞在I/O操作中(参考javanio),调用中断方法的通道会被关闭,线程会收到ClosedByInterruptException,并设置中断标志;...如何理解中断标志?“如果被中断线程当前正在调用Object#wait、Thread#join、Thread#sleep方法,会收到InterruptedException,会清除中断标志”,案例中的代码正好符合这个,如果我们改一下案例代码如下:@Testpublicvoidtest(){ExecutorServiceexecutorService=Executors.newSingleThreadExecutor();Futurefuture=executorService.submit(()->{while(!Thread.interrupted()){System.out.println("rung....");try{Thread.sleep(1000);}catch(InterruptedExceptionex){System.err.println("中断");}}});ThreadUtils.sleep(1000);future.cancel(true);try{future.get();}catch(InterruptedException|CancellationException|ExecutionExceptione){e.printStackTrace();}ThreadUtils.sleep(1000*60);}执行这段代码,你会发现死循环根本没有退出,正是因为Thread#sleep方法被中断了,JVM不会设置中断标志,但会抛出InterruptedException。在其他情况下,JVM只会设置中断标志,不会抛出InterruptedException。如果我们不处理中断信号,中断信号不会影响程序的继续执行。@Testpublicvoidtest2(){ExecutorServiceexecutorService=Executors.newSingleThreadExecutor();Futurefuture=executorService.submit(()->{intnumber=0;while(!Thread.interrupted()){number++;}System.out.println(数字);});ThreadUtils.sleep(1000);future.cancel(true);try{future.get();}catch(InterruptedException|CancellationException|ExecutionExceptione){e.printStackTrace();}ThreadUtils.sleep(1000*60);}本例不存在I/O操作造成的阻塞,因为调用中断方法后,线程只是设置了中断标志,我们将中断标志作为顺序退出条件,运行本例,我们会看到,线程中断后,任务终止。反之,如果我们不处理中断标志,那么就等着IDEA进程卡住。