多线程并发篇——如何停止线程

3,090 阅读4分钟

  笔者是广州的java程序员,刚毕业半年,工作之余写博客,如果觉得我的文章写得不错,可以关注我的微信公众号(J2彬彬),里面会有更多精彩内容。从2018年8月份开始写博客,希望日后写出更多通俗易懂的技术文章与大家一同分享。

前言

  你有没有想过,如何停止一个线程?很多人首先会想到Thread.stop()方法,但是这个方法已经过时,不推荐使用,因为这个方法会带来安全问题,什么安全问题呢?后面会有详细说明。我们先讲讲目前JDK API推荐使用停止线程的方法Thread.interrupt()方法。

Thread.interrupt()

  既然不能直接stop线程,那么只有一种方法可以让线程结束,那就是让run方法运结束。   Thread.interrupt()代表的意思是“停止,中止”。但是这个方法需要加入一个判断才可以完成线程的停止。一旦检测到线程处于中断状态,那么就有机会结束run方法。   下面以一个代码示例看看它是如何停止线程的?

一、interrupt()停止线程
package com.bingo.thread.stopThread;

/**
 * Created with IntelliJ IDEA.
 * Description: 停止线程不推荐使用stop方法,此方法不安全,我们可以使用Thread.interrupt()
 * User: bingo
 */
public class Run {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.interrupt();
        System.out.println("end...");
    }
}


class MyThread extends Thread{

    @Override
    public void run() {
        super.run();

        for (int i = 0; i < 10000 ; i++) {

            if(this.isInterrupted()){
                System.out.println("已经是停止状态了,我要退出了");
                break;
            }
            System.out.println("i="+(i+1));
        }
    }
}

运行结果:

i=1
......
i=3102
i=3103
i=3104
i=3105
i=3106
i=3107
i=3108
end...
已经是停止状态了,我要退出了

  运行结果我们可以看到,当myThread线程的循环运行到i=3108的时候,由于线程被中断,而跳出循环,这个例子很好诠释了interrupt方法的作用。

二、interrupt可以清除线程的冻结状态,让线程恢复到可运行的状态上来
package com.bingo.thread.stopThread;

/**
 * Created with IntelliJ IDEA.
 * Description: interrupt可以清除线程的冻结状态,让线程恢复到可运行的状态上来。
 * User: bingo
 */
public class Run2 {

    public static void main(String[] args) {
        MyThread2 thread = new MyThread2();
        thread.start();
        thread.interrupt();
        System.out.println("main end...");
    }
}

class MyThread2 extends Thread{

    @Override
    public void run() {
        System.out.println("run begin...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            System.out.println("run 在沉睡中被中止,进入catch");
            e.printStackTrace();
        }
        System.out.println("run end...");
    }
}

运行结果:

main end...
run begin...
run 在沉睡中被中止,进入catch
run end...
java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at com.bingo.thread.stopThread.MyThread2.run(Run2.java:26)

  从运行结果我们可以看到,本来run方法睡眠时间为1000秒,但是打印结果却是瞬间的,其实sleep已经被interrupt方法给打断,此时线程冻结状态被清除,并抛出异常,被catch捕获,打印异常信息。

暴力停止——Stop

  下图是JDK API对stop方法的描述,可以看到已过时,不推荐使用,并告诉我们此方法为何不安全?

  如果某个线程加锁,stop方法停止该线程时会把锁释放掉,可能造成数据不一致的情况。下面代码可以说明此问题。

package com.bingo.thread.stopThread;

/**
 * Created with IntelliJ IDEA.
 * Description: stop()方法为何不安全?下面例子可解答
 * User: bingo
 */
public class StopTest {

    public static void main(String[] args) {

        try {

            SynchrionzedObject object = new SynchrionzedObject();
            MyThread3 t = new MyThread3(object);
            t.start();

            Thread.sleep(500);

            t.stop();

            System.out.println(object.getUsername()+" "+object.getPassword());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class SynchrionzedObject{

    private String username = "a";

    private String password = "aa";

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public synchronized void printString(String username,String password){
        try {

            this.username = username;
            Thread.sleep(10000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

class MyThread3 extends Thread{

    private SynchrionzedObject object;

    public MyThread3(SynchrionzedObject object){
        this.object = object;
    }

    @Override
    public void run() {
        object.printString("b", "bb");
    }
}

运行结果:

b aa

  从上面例子我们可以看到虽然printString方法加了锁,但是run方法运行过程中突然被stop了,锁被释放,MyThread线程只对username进行了赋值,而password赋值动作未执行,此时造成数据不一致。

最后

  其实java多线程很多方法内部都是native方法,也就是基于JVM内部实现的,所以我们有必要结合JVM一起学习这部分的内容。技术的进步需要每个小小的积累,才能走得更远,更长久。