java多线程编程--如何终止一个线程

1. Thread.stop()函数

stop()函数终止线程就像是强行拔掉电源线关机一样,可能会带来未知风险,因此目前不再推荐使用这种方式。请忘记它吧~~

2. 改变标志变量状态

通常我们会在线程中使用一个标志变量来控制线程的运行,如:

public class TestCallable implements Runnable {

    private boolean running = true;

    public void stop() {
        this.running = false;
    }

    public void run() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            while (running) {
                System.out.println("线程正在运行中...");
                Thread.sleep(20000);
            }
            System.out.println("线程被终止.");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        try {
            TestCallable callable = new TestCallable();
            Thread th = new Thread(callable);
            th.start();
            Thread.sleep(1000);
            callable.stop();
            System.out.println("已下达终止线程命令。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行上述代码就能发现,线程阻塞在reader.readLine()时,即使主线程改变了标志变量,但是并不能立即结束子线程,只有等待阻塞被打破,且运行到下一次循环条件判断的时候才能终止。所以在使用这种方法时,应该考虑到阻塞这种情况。当然,如果整个循环内的操作属于同一事务时,这种方法倒很不错。

3. 中断函数interrupt()

网上有很多论调说,终止线程的正确处理方式是使用interrupt中断,但真的是这样吗?实践出真知,拭目以待吧!

如上2所述,如果线程中有Thread.sleep()阻塞时,改变标识变量无法达到终止线程的目的,那么此时可以使用Thread类的中断函数interrupt();

如:

public class TestCallable extends Thread {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    public void stopThread() {
      interrupt();
    }

    public void run() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            reader.close();
            while (!isInterrupted()) {
                System.out.println("线程正在运行中...");
                Thread.sleep(20000);
            }
            System.out.println("线程被终止.");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            TestCallable cal = new TestCallable();
            cal.start();
            Thread.sleep(2000);
            cal.stopThread();
            System.out.println("已下达终止线程命令。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调用线程对象的interrupt()时,sleep的线程会抛出InterruptedException异常,从而中断循环,终止线程。

但是如果是IO如输入这些阻塞,中断的方法又不起作用了,还有就是对于没有阻塞的线程,调用interrupt()是达不到终止线程的效果的。

那么,interrupt()中断在线程的哪些阻塞操作中能起到抛出InterruptedException作用呢?

我们来做一个实验,代码如下:

public class TestCallable {

    public static void main(String[] args) {

        // 测试无阻塞的情况,进行中断
        Thread thread1 = new Thread(){
            public void run(){
                try {
                    long time = System.currentTimeMillis();
                    while(System.currentTimeMillis()-time<5000){
                    }
                    System.out.println("A1");
                } catch(Exception e) {
                    System.out.println("B1");
                    System.out.println("B1 : " + e.toString());
                }
            }
        };
        thread1.start();
        thread1.interrupt();

        //在线程sleep状态下进行中断
        Thread thread2 = new Thread(){
            public void run(){
                try {
                    this.sleep(5000);
                    System.out.println("A2");
                } catch (Exception e) {
                    System.out.println("B2");
                    System.out.println("B2 : " + e.toString());
                }
            }

        };

        thread2.start();
        thread2.interrupt();

        //在线程wait状态下进行中断,其中wait()没有在同步块中
        Thread thread3 = new Thread(){
            public void run(){
                try {
                    this.wait(5000);
                    System.out.println("A3");
                } catch (Exception e) {
                    System.out.println("B3");
                    System.out.println("B3 : " + e.toString());
                }
            }

        };

        thread3.start();
        thread3.interrupt();

        //在线程wait状态下进行中断,其中wait()在同步块中
        Thread thread4 = new Thread(){
            public void run(){
                try {
                    synchronized(this){
                        this.wait(5000);
                        System.out.println("A4");}
                } catch (Exception e) {
                    System.out.println("B4");
                    System.out.println("B4 : " + e.toString());
                }
            }
        };

        thread4.start();
        thread4.interrupt();

        // join阻塞时进行中断
        Thread thread5 = new Thread() {
            public void run() {
                try {
                    this.join(5000);
                    System.out.println("A5");
                } catch (Exception e) {
                    System.out.println("B5");
                    System.out.println("B5 : " + e.toString());
                }
            }
        };
        thread5.start();
        thread5.interrupt();
    }
}

输出结果:

B2
B4
B4 : java.lang.InterruptedException
B3
B3 : java.lang.IllegalMonitorStateException
B5
B2 : java.lang.InterruptedException: sleep interrupted
B5 : java.lang.InterruptedException
A1

结果分析:

输出A1: 说明在无阻塞的时候,中断没有作用。

输出B2 : java.lang.InterruptedException: sleep interrupted 说明sleep操作阻塞时,中断生效。

输出B3 : java.lang.IllegalMonitorStateException 非中断引起的异常,这是wait()用法错误,这里就不详述。但正好可以看出,不论什么异常,都可以中断线程~

输出B4: java.lang.InterruptedException 说明wait操作阻塞时,中断生效。

输出B5:java.lang.InterruptedException 说明join操作阻塞时,中断生效。

所以,针对线程的上述阻塞,都可以使用interrupted()方法中断线程,应该说,interrupted是对处于WAITTING 和TIME_WAITTING状态下的线程有用。

那么问题来了,如果是其他阻塞情况呢?如IO阻塞,又该如何中断阻塞?其实从上面的例子可以大致得出结论:中断阻塞就可以。

如:

public class TestCallable implements Runnable {

    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    public void stop() {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        try {
            while (true) {
                System.out.println("线程开始执行...");
                String cmd = reader.readLine();
            }
        } catch (IOException e) {
            System.out.println("IO异常,线程结束。");
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        TestCallable callable = new TestCallable();
        Thread th = new Thread(callable);
        th.start();
        callable.stop();

    }

}

通过关闭通道,抛出一个异常来中断阻塞,达到终止线程的目的。对于网络IO也是如此。

4. 总结

通过上述分析可知,要真正按我们的预期正确结束一个线程真的不容易!

不过总得说来,终止线程有两类方法:

针对没有阻塞的情况:设置标志变量,让线程正常自然死亡,和谐!

针对有阻塞的情况:中断阻塞,靠抛出异常终止线程,看似暴力,但如果是我们预期的异常,那也是安全的!

所以,要结合你自身程序的应用场景以及代码编写的具体情况,来确定最终方案。

通过上述分析总结,我个人认为较好的方式是:

(1)不管有没有阻塞情况,都用标志变量控制线程循环。

网上有人使用如下的方式来代替标志变量,其实是不正确的,因为这样就依赖于线程的中断状态,可能导致线程非预期终止。

public void run() {
    while(!Thread.currentThread().isInterrupted()) {
      ...
    }
}

(2)用一个函数封装终止线程的操作,暴漏给外部调用。

(3)针对线程中出现的阻塞情况,使用相应的中断方法,如线程的WATTING,TIME_WAITTING状态,可以用interrupted()方法,对于IO阻塞,可以关闭IO通道等。

public class TestCallable implements Runnable {
    // 不管是不是阻塞情况,都用标志变量控制线程循环
    private boolean running;

    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    /**
     * 用一个函数封装终止线程的操作
     */
    public void stop() {
        running = false;

        /*
         * 针对线程中出现的阻塞情况,使用相应的中断方法。
         * 如线程的WAITTING,TIME_WAITTING状态,可以用interrupted()
         * 对IO阻塞,可以关闭IO通道等。
         */
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        try {
            running = true;
            while (running) {
                System.out.println("线程开始执行...");
                String cmd = reader.readLine();
            }
        } catch (IOException e) {
            System.out.println("IO异常,线程结束。");
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        TestCallable callable = new TestCallable();
        Thread th = new Thread(callable);
        th.start();
        callable.stop();
    }

}

5. 碎碎念

本来只想总结一下终止线程,没想到又扯出了线程状态,这个就下回再详解吧。真是点带线,线带面啊,真真是“知识的海洋”......

不过,收获不小,对于网络上得种种说法,进行试验、排错,最后得出自己的解法。如果您有更好的建议,热烈欢迎!!

最近公司出推行新制度了,比较混乱的状态,偷闲梳理下博客,优哉游哉~~

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-13 08:58:43

java多线程编程--如何终止一个线程的相关文章

java多线程编程--如何开始一个线程

如何开始一个线程 1. java多线程的实现方式 1.1 继承Thread类 定义类如下: public SubThread extends Thread { @override public void run() { ... } } 使用时: Thread subThread = new SubThread(); subThread.start(); 可以使用Thread类已有的函数进行操作. 1.2 实现Runnable接口 定义类如下: public SubThread implement

java多线程编程(1) 线程的基本知识

在前面研究过多线程与进程的区别. 这里在稍微总结一下: 进程:程序动态的一次执行过程. 线程:可以只是程序员的一部分的执行过程 每个进程有多个线程组成,在java程序中,至少两个线程一个是垃圾回收线程和main线程. 线程占有的资源更少,早java中就是每个线程都有自己的工作区,就是有自己独立的栈空间.多个线程共享一些内存资源,堆是共享的. 多线程的力度小,并发度高,这样系统的吞吐量就很大,只有好处吗?调度和执行线程是需要资源的,就是像是数据库中的索引和数据库中的锁一个道理,并发会带来什么问题呢

Java多线程编程7--拾遗增补--线程组

可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程.这样的组织结构有些类似于树的形式,如图所示. 线程组的作用是,可以批量的管理线程或线程组对象,有效地对线程或线程组对象进行组织. 1.线程对象关联线程组:1级关联 所谓的1级关联就是父对象中有子对象,但并不创建子孙对象.这种情况经常出现在开发中,比如创建一些线程时,为了有效地对这些线程进行组织管理,通常的情况下是创建一个线程组,然后再将部分线程归属到该组中.这样的处理可以对零散的线程对象进行有效的组织与规划

Java多线程编程7--拾遗增补--线程的状态(new,runnable,terminated,timed_waiting,waiting,blocked)

线程对象在不同的运行时期有不同的状态,状态信息就存在于Thread内部类的State枚举类中 public enum State { /** * new状态是线程实例化后还从未执行start()方法时的状态 */ NEW, /** * runnable状态是线程进人运行的状态 */ RUNNABLE, /** * blocked状态出现在某一个线程在等待锁的时候. */ BLOCKED, /** * waiting是线程执行了Object.wait()方法后所处的状态 */ WAITING, /

Java多线程编程(三)线程的优先级、同步与死锁

线程的优先级: 线程的优先级分为三种,分别是: 1-MIN_PRIORITY 10-MAX_PRIORITY 5-NORM_PRIORITY 如果什么都不设置默认值是5 线程的优先级可以影响线程的执行顺序,当然这里指的是有可能影响,不会一定影响.在默认状态下(比如说主线程)它的默认值是5 具体代码演示: package com.yeqc.thread; class ThRun implements Runnable{ @Override public void run() { for(int i

Java多线程编程基础之线程对象

在进入java平台的线程对象之前,基于基础篇(一)的一些问题,我先插入两个基本概念. [线程的并发与并行] 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent).而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel). 在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储

Java多线程编程(学习笔记)

一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过多线程的使用,可以编写出非常高效的程序.但如果创建了太多的线程,程序执行的效率反而会降低. 同时上下文的切换开销也很重要,如果创建太多的线程,CPU花费在上下文的切换时间将对于执行程序的时间. 二.Java多线程编程 概念 在学习多线程时,我们应该首先明白另外一个概念. 进程:是计算机中的程序关于某

Java多线程编程详解

线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synch

Java多线程编程要点

Java多线程编程要点 认识Thread和Runnable Java中 实现多线程有两种途径:继承Thread类或者实现Runnable接口.Runnable是接口,建议用接口的方式生成线程,因为接口可以实现多继承, 况且Runnable只有一个run方法,很适合继承.在使用Thread的时候只需继承Thread,并且new一个实例出来,调用 start()方法即可以启动一个线程. Thread Test = new Thread(); Test.start(); 在使用Runnable的时候需