停掉线程的方法

停掉线程的方法

参考 :https://blog.csdn.net/bbj12345678/article/details/120409799?ops_request_misc=&request_id=&biz_id=102&utm_term=java%20%E7%BA%BF%E7%A8%8B%E4%B8%AD%E6%96%AD&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-120409799.nonecase&spm=1018.2226.3001.4187

启动线程需要调用 Thread 类的 start() 方法,并在 run() 方法中定义需要执行的任务

对于 Java 而言,最正确的停止线程的方式是使用 interrupt 音彻瑞普特。但 interrupt仅仅起到通知被停止线程的作用。

而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

呐为什么不能强制停止?

Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题。

比如

线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止。如果选择立即停止就可能造成数据不完整,不管是中断命令发起者,还是接收者都不希望数据出现问题。

怎么调用程序方法停掉线程

while (!Thread.currentThread().isInterrupted()  && more work to do) {
    do more work
}

一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true。每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位,如果标记位被设置成 true,就说明有程序想终止该线程。

class Thread3 extends Thread{
    public void run(){
        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println("Someone interrupted me.");
            }
            else{
                System.out.println("Thread is Going...");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread3 t = new Thread3();
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
} 

在main线程sleep的过程中由于t线程中isInterrupted()为false所以不断的输出”Thread is going”。当调用t线程的interrupt()后t线程中isInterrupted()为true。此时会输出Someone interrupted me.而且线程并不会因为中断信号而停止运行。因为它只是被修改一个中断信号而已。

可以看到在 while 循环体判断语句中,首先通过 Thread.currentThread().isInterrupt() 判断线程是否被中断,随后检查是否还有工作要做。&& 逻辑表示只有当两个判断条件同时满足的情况下,才会去执行下面的工作。

package com.letu.test;

public class StopThread implements Runnable {
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        //启动子线程
        thread.start();
        Thread.sleep(5);
        //中断线程
        thread.interrupt();
    }
}

首先判断线程是否被中断,然后判断 count 值是否小于 1000。

创建线程,调用start启动线程,然后调用run方法执行,最后中断停止while循环

最后结果没有输出到1000就停止

这就属于通过 interrupt 正确停止线程的情况

在sleep期间也可以感受到中断
 public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (!Thread.currentThread().isInterrupted() && num <= 1000) {
                    System.out.println(num);
                    num++;
                    Thread.sleep(1000000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }

一开始线程正常进入循环,然后休眠,但是主线程5毫秒就给这个线程中断;

输出结果报错:

0
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.letu.test.StopThread.lambda$main$0(StopThread.java:29)
	at java.lang.Thread.run(Thread.java:748)

InterruptedException 休眠中断 sleep interrupted

Object.wait, Thread.join和Thread.sleep等可以让线程进入阻塞的方法使线程休眠了,没有占用CPU运行的线程是不可能给自己的中断状态置位的。这就会产生一个InterruptedException异常。同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。

所以要合理利用try catch处理

//Interrupted的经典使用代码    
public void run(){    
           try{    
                 ....    
                 while(!Thread.currentThread().isInterrupted()&& more work to do){    
                        // do more work;    
                 }    
            }catch(InterruptedException e){    
                        // thread was interrupted during sleep or wait    
            }    
            finally{    
                       // cleanup, if required    
            }    
}    

如果线程被阻塞,它便不能核查共享变量,也就不能停止。这在许多情况下会发生,例如调用Object.wait()、ServerSocket.accept()和DatagramSocket.receive()时,他们都可能永久的阻塞线程。即使发生超时,在超时期满之前持续等待也是不可行和不适当的;

所以,要使用某种机制使得线程更早地退出被阻塞的状态。很不幸运,不存在这样一种机制对所有的情况都适用,但是,根据情况不同却可以使用特定的技术。使用Thread.interrupt()中断线程正如Example1中所描述的;

Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

因此,如果线程被上述几种方法阻塞,正确的停止线程方式是设置共享变量,并调用interrupt()(注意变量应该先设置)。

如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。在任何一种情况中,最后线程都将检查共享变量然后再停止

下面示例描述了该技术。

class Example3 extends Thread {
 
    volatile boolean stop = false;
 
    public static void main(String args[]) throws Exception {
        Example3 thread = new Example3();
 
        System.out.println("Starting thread...");
        thread.start();
 
        Thread.sleep(3000);
 
        System.out.println("Asking thread to stop...");
 
        /*
         * 如果线程阻塞,将不会检查此变量,调用interrupt之后,线程就可以尽早的终结被阻
         * 塞状 态,能够检查这一变量。
         * */
        thread.stop = true;
 
        /*
         * 这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退
         * 出阻 塞的状态
         * */
        thread.interrupt();
 
        Thread.sleep(3000);
        System.out.println("Stopping application...");
        System.exit(0);
    }
 
    public void run() {
        while (!stop) {
            System.out.println("Thread running...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // 接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态
                System.out.println("Thread interrupted...");
            }
        }
 
        System.out.println("Thread exiting under request...");
    }
}

把握几个重点:
stop变量、run方法中的sleep()、interrupt()、InterruptedException。

串接起来就是这个意思:当我们在run方法中调用sleep(或其他阻塞线程的方法)时,如果线程阻塞的时间过长,比如10s,那在这10s内,线程阻塞,run方法不被执行;

但是如果在这10s内,stop被设置成true,表明要终止这个线程,但是,现在线程是阻塞的,它的run方法不能执行,自然也就不能检查stop,所 以线程不能终止;

这个时候,我们就可以用interrupt()方法了:

我们在thread.stop = true; 语句后调用thread.interrupt()方法, 该方法将在线程阻塞时抛出一个中断信号,该信号将被catch语句捕获到,一旦捕获到这个信号,线程就提前终结自己的阻塞状态,这样,它就能够 再次运行run 方法了,然后检查到stop = true,while循环就不会再被执行,在执行了while后面的清理工作之后,run方法执行完 毕,线程终止。

当代码调用中须要抛出一个InterruptedException, 你可以选择把中断状态复位, 也可以选择向外抛出InterruptedException, 由外层的调用者来决定.

不是所有的阻塞方法收到中断后都可以取消阻塞状态, 输入和输出流类会阻塞等待 I/O 完成,但是它们不抛出 InterruptedException,而且在被中断的情况下也不会退出阻塞状态.

尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式即 tryLock(long time, TimeUnit unit)。

停止线程的其他方式
void shutdown;
boolean isShutdown;
boolean isTerminated;
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
List<Runnable> shutdownNow;
1.shutdown

调用 shutdown() 方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。

但这并不代表 shutdown() 操作是没有任何效果的,调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。

2.isTerminated()

这个方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了,因为我们刚才说过,调用 shutdown 方法之后,线程池会继续执行里面未完成的任务,不仅包括线程正在执行的任务,还包括正在任务队列中等待的任务。

比如此时已经调用了 shutdown 方法,但是有一个线程依然在执行任务,那么此时调用 isShutdown 方法返回的是 true ,而调用 isTerminated 方法返回的便是 false ,因为线程池中还有任务正在在被执行,线程池并没有真正“终结”。直到所有任务都执行完毕了,调用 isTerminated() 方法才会返回 true,这表示线程池已关闭并且线程池内部是空的,所有剩余的任务都执行完毕了。

3.awaitTermination()

awaitTermination(),它本身并不是用来关闭线程池的,而是主要用来判断线程池状态的。比如我们给 awaitTermination 方法传入的参数是 10 秒,那么它就会陷入 10 秒钟的等待,

直到发生以下三种情况之一:

  • 等待期间(包括进入等待状态之前)线程池已关闭并目所有已提交的任务

    (包括正在执行的和队列中等待的都执行完毕,相当于线程池已经“终结”了,方法便会返回true

  • 等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false

  • 等待期间线程被中断,方法会抛出 Interruptedexception异常

4.shutdownNow()

最后一个方法是 shutdownNow(),也是 5 种方法里功能最强大的,它与第一种 shutdown 方法不同之处在于名字中多了一个单词 Now,也就是表示立刻关闭的意思。参考这里:shutdown 和 shutdownNow 的区别

在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试。

public List<Runnable> shutdownNow() { 
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
 
    try { 
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally { 
        mainLock.unlock();
    } 
 
    tryTerminate();
    return tasks;
 }

源码中有一行 interruptWorkers() 代码,这行代码会让每一个已经启动的线程都中断,这样线程就可以在执行任务期间检测到中断信号并进行相应的处理,提前结束任务。

这里需要注意的是,由于 Java 中不推荐强行停止线程的机制的限制,即便我们调用了 shutdownNow 方法,如果被中断的线程对于中断信号不理不睬,那么依然有可能导致任务不会停止。

总结 :最正确的停止线程的方式是使用 interrupt

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值