美文网首页就该这么学并发
04. 就该这么学并发 - 线程的结束

04. 就该这么学并发 - 线程的结束

作者: 码哥说 | 来源:发表于2020-06-16 10:03 被阅读0次

前言

接上章“线程的阻塞”,我们继续讲线程的生命周期中最后一个状态“销毁”

销毁(Dead)

线程会以如下3种方式结束,结束后就处于死亡状态:

- run()或call()方法执行完成,线程正常结束

- 线程抛出一个未捕获的Exception或Error

- 直接调用该线程stop()(已废弃,不推荐使用)方法来结束该线程,
  该方法容易导致死锁,通常不推荐使用

线程一旦死亡,就不能复生.

如果在一个死去的线程上调用start()方法,会抛出

java.lang.IllegalThreadStateException

stop()被废弃的原因

补充下stop()被废弃的原因,

因为它不安全,它会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止

假如一个线程正在执行:

synchronized void { 
    x = 3; 
    y = 4;
} 

由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,

而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据( 也就是只有x=3被执行了 )

安全的结束线程

线程属于一次性消耗品,在执行完run()方法之后线程便会正常结束了,

但有时我们需要线程执行循环处理任务,

在这种情况下,一般是将这些任务放在一个死循环中,如while循环,

当需要结束线程时,如何退出线程呢?

Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是不安全的!想要安全有效的结束一个线程,可以使用下面的方法.

- 正常执行完run方法,然后结束掉;

- 使用退出标识符终止线程 

- 使用interrupt()方法中断当前线程 

正常执行的我们自然不用讲,下面举个例子

使用退出标志终止线程

public class Test {
    public static void main(String[] args) throws Exception {
        Thread stopThread = new Thread(new Runnable(){
            int i=0;  
            @Override
            public void run() {
                while (true) {  
                    if(i==5)  
                        break;  
                    i++;  
                    System.out.println(i);    
                }  
            }
        });
        stopThread.start();
    }
}

使用标识符来结束一个线程,是一个不错的方法,但其也有弊端

如果该线程是处于sleep、wait、join的状态时候,while循环就不会执行

那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了.

所以,此时可以使用interrupt这个巧妙的方式结束掉这个线程.我们先来看看sleep、wait、join方法的声明:

public final void wait() throws InterruptedException
 
public static native void sleep(long millis) throws InterruptedException

public final void join() throws InterruptedException

可以看到,这三者有一个共同点,

都抛出了一个InterruptedException的异常

在什么时候会产生这样一个异常呢?

每个Thread都有一个中断状状态,默认为false.
可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态.
可以通过Thread对象的interrupt()方法将中断状态设置为true.

当一个线程处于sleep、wait、join这三种状态之一的时候,
如果此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态重新设置为false.

举个简单的例子验证下以上的特性

class StopThread extends Thread {
    int i = 1;

    @Override
    public void run() {
        while (true) {
            System.out.println("计数器: "+i);
            System.out.println("Interrupted: "+this.isInterrupted());
            try {
                System.out.println("线程将睡眠5s");
                Thread.sleep(5000);
                this.interrupt();
            } catch (InterruptedException e) {
                System.out.println("异常捕获,Interrupted: " + this.isInterrupted());
                return;
            }
            i++;
        }
    }
}
public class Test {
    public static void main(final String[] args) throws Exception {
        StopThread stopThread = new StopThread();
        stopThread.start();
    }
}

结果

计数器: 1
Interrupted: false
线程将睡眠5s
计数器: 2
Interrupted: true
线程将睡眠5s
异常捕获了false

可以看到,在第一次循环中,睡眠5秒,然后将中断状态设置为true.当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了.然后中断状态又被重新自动设置为false了.

由此,我们可以用interrupt来优雅的结束掉线程

class StopThread extends Thread {
    int i = 1;

    @Override
    public void run() {
        while (true) {
            System.out.println("计数器: "+i);
            System.out.println("Interrupted: "+this.isInterrupted());
            try {
                System.out.println("线程将睡眠5s");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                System.out.println("异常捕获,Interrupted: " + this.isInterrupted());
                return;
            }
            i++;
        }
    }
}
public class Test {
    public static void main(final String[] args) throws Exception {
        StopThread stopThread = new StopThread();
        stopThread.start();
        Thread.sleep(2000);
        stopThread.interrupt();
    }
}

执行结果可能有两种

情况1

计数器: 1
Interrupted: false
线程将睡眠5s
异常捕获,Interrupted: false

子线程start(),主线程睡眠2秒醒来后,调用子线程的interrupt(),此时,子线程正好在sleep(2000),所以
循环第一次执行就结束了.

情况2

计数器: 1
Interrupted: false
线程将睡眠5s
计数器: 2
Interrupted: true
线程将睡眠5s
异常捕获,Interrupted: false

子线程start(),主线程睡眠2秒醒来后,调用子线程的interrupt(),此时子线程还没有处于sleep状态.然后再第2次while循环的时候,再次进入sleep状态,立马抛出InterruptedException异常

最后,说明下stop()interrupt()的区别

stop()调用后线程直接“强制停止”,不会等到run()方法执行完

interrupt()则会“优雅停止”,等到run()方法执行完再停止

欢迎关注我

技术公众号 “CTO技术”

相关文章

网友评论

    本文标题:04. 就该这么学并发 - 线程的结束

    本文链接:https://www.haomeiwen.com/subject/bolszhtx.html