Java 正确终止线程的方法

Thread类中有一个已经废弃的 stop() 方法,它可以终止线程,但由于它不管三七二十一,直接终止线程,所以被废弃了。比如,当线程被停止后还需要进行一些善后操作(如,关闭外部资源),使用这个方法就无能为力了。可以通过线程中断来实现线程终止。

首先来看一下Java线程中断的一些内容:

  • Java平台为每个线程维护了一个布尔型的中断标记,可以通过下列方法获取该标记的值:

interrupt() 中断某个线程
            isInterrupted() 返回该线程的中断标记
            interrupted() 返回并重置该线程的中断标记(置为false)

  • 中断仅是发起线程对目标线程的一种请求,也就是说,目标线程对这种请求可以相应,也可以忽略。
  • Java标准库中与线程阻塞相关的方法对中断的相应方式都是抛出 InterruptedException 异常,并且按照惯例,抛出异常前都会重置中断标记为false,因此这些方法会清空线程的中断标记。
  • Java标准库中与线程阻塞相关的方法在进行阻塞前会判断中断标记是否为true,为true则抛出异常;如果在阻塞后调用中断方法的话,那么JVM会设置该线程的中断标记,然后将该线程唤醒,因此中断具有唤醒线程的作用。

由上面几点和第二句加粗的话可知,可以使用线程中断来实现线程终止,只要目标线程判断一下中断标记即可,即使被中断的线程正处于阻塞状态,也能把他唤醒起来终止;由第一句加粗的话可知,直接使用线程中断实现线程终止是存在风险的,因为可能调用了一些Java标准库的阻塞方法,而导致了中断标记被清空,也就无法获得中断标记了(总是false),因此需要自己创建一个中断标记配合使用。

如,下面是一个可中断的任务执行器,他会在每次执行任务前,判断一下自定i的终止标记和剩余的任务数(善后);提供的shutdown方法除了将工作线程中断外(主要作用是唤醒可能处于阻塞状态的任务),还会将终止交集 terminated 置为 true。

执行 main 方法,可以发现,首先会打印出“客户端调用了 shutdown 方法”,然后过了四秒,main线程才会终止,可知shutdown方法正确地将目标线程终止了。关于“按照惯例,Java标准库中抛出InterruptedException异常的和线程相关的阻塞方法会清空中断标记”,可以将条件中的 !interminated 替换成 !Thread.currentThread().isInterrupted(),然后再执行main方法测试,可以发现main线程始终无法终止,因为 sleep() 方法清空了中断标记,所以  !Thread.currentThread().isInterrupted() 始终为true,导致工作线程始终无法终止。

public class TerminableTaskRunner {
    // 存储要执行的任务
    private final BlockingQueue<Runnable> tasks;
    // 线程终止标志
    private volatile boolean terminated;
    // 剩余的任务数
    private final AtomicInteger count;
    // 实际执行任务的线程
    private volatile Thread workThread;
 
    public TerminableTaskRunner(int capacity) {
        this.tasks = new LinkedBlockingDeque<>(capacity);
        this.count = new AtomicInteger(0);
        this.workThread = new WorkThread();
        workThread.start();
    }
 
    public void submit(Runnable task) {
        this.tasks.add(task);
        this.count.incrementAndGet();
    }
 
    public void shutdown() {
        terminated = true; // 线程终止标志,由于中断标志可能会被覆盖,所以需要自己创建一个标志
        if (workThread != null)
            workThread.interrupt(); // 唤醒线程
    }
 
    private class WorkThread extends Thread {
        @Override
        public void run() {
            Runnable task;
            try {
                while (!terminated || tasks.size() >= 1) {
                    task = tasks.take();
                    try {
                        task.run(); // 可能会清空当前线程的中断标记,如task.run()在内部调用的阻塞方法抛出了InterruptedException
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                    count.decrementAndGet();
                }
            } catch (InterruptedException e) {
                // 一旦调用shutdown且tasks.take()阻塞住,就抛出该异常,没有任务要执行,直接终止
                workThread = null;
            }
        }
    }
 
    public static void main(String[] args) {
        TerminableTaskRunner taskRunner = new TerminableTaskRunner(4);
        for (int i = 0; i < 4; i++) {
            taskRunner.submit(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("客户端调用了 shutdown 方法");
                }
            });
        }
        taskRunner.shutdown();
 
    }
}

以上就是Java 正确终止线程的方法的详细内容,更多关于Java 终止线程的资料请关注我们其它相关文章!

(0)

相关推荐

  • interrupt()和线程终止方式_动力节点Java学院整理

    中断线程 线程的thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡.还是等待新的任务或是继续运行至下一步,就取决于这个程序本身.线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true).它并不像stop方法那样会中断一个正在运行的线程. 判断线程是否被中断 判断某个线程是否已被发送过中断请求,请使用Thread.currentThread().isInterrupted()方法(因为它将线程中断标示位

  • Java实现终止线程池中正在运行的定时任务

    最近项目中遇到了一个新的需求,就是实现一个可以动态添加定时任务的功能.说到这里,有人可能会说简单啊,使用quartz就好了,简单粗暴.然而quartz框架太重了,小项目根本不好操作啊.当然,也有人会说,jdk提供了timer的接口啊,完全够用啊.但是我们项目的需求完全是多线程的模型啊,而timer是单线程的,so,楼主最后还是选择了jdk的线程池. 线程池是什么 Java通过Executors提供四种线程池,分别为: newCachedThreadPool :创建一个可缓存线程池,如果线程池长度

  • Java中终止线程的三种方法

    Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 这些终止线程运行的方法已经被废弃,使用它们是极端不安全的! 1.线程正常执行完毕,正常结束 也就是让run方法执行完毕,该线程就会正常结束. 但有时候线程是永远无法结束的,比如while(true). 2.监视某些条件,结束线程的不间断运行 需要while()循环在某以特定条件下退出,最直接的办法就是设一个boolean标志,并通过设置这个标志来控制循环

  • Java并发编程示例(六):等待线程执行终止

    在某些场景下,我们必须等待线程执行完成才能进行下一步工作.例如,某些程序在开始执行之前,需要先初始化一些资源.这时,我们可以启动一个线程专门来做初始化任务,等到线程任务完成后,再去执行其他部分. 为此,Thread类为我们提供了join()方法.当我们使用线程对象调用此方法时,正在掉调用的线程对象将被推迟到被调用对象执行完成后再开始执行. 在本节,示例程序演示等待初始化方法完成后,再去执行其他任务. 知其然 按照下面所示步骤,完成示例程序. 1.创建一个名为DataSourcesLoader的类

  • Java语言多线程终止中的守护线程实例

    Java中线程分为两种类型:用户线程和守护(服务)线程.通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程;不设置则默认为用户线程. 结束单线程用 Thread.interrupt() 方法,多线程结束则需要设置守护线程.当不存在用户线程时,守护线程就会全部终结(可以理解为:守护线程是服务线程,用户线程是被服务线程,用户线程(被服务线程)全都没有了,服务线程便没有存在意义而自动终结) 例子: class StopThr

  • Java如何使用interrupt()终止线程

    一.interrupt() 说明 interrupt()的作用是中断本线程. 本线程中断自己是被允许的:其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限.这有可能抛出SecurityException异常. 如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sl

  • 详解Java编程中线程的挂起、恢复和终止的方法

    有时,线程的挂起是很有用的.例如,一个独立的线程可以用来显示当日的时间.如果用户不希望用时钟,线程被挂起.在任何情形下,挂起线程是很简单的,一旦挂起,重新启动线程也是一件简单的事. 挂起,终止和恢复线程机制在Java 2和早期版本中有所不同.尽管你运用Java 2的途径编写代码,你仍需了解这些操作在早期Java环境下是如何完成的.例如,你也许需要更新或维护老的代码.你也需要了解为什么Java 2会有这样的变化.因为这些原因,下面内容描述了执行线程控制的原始方法,接着是Java 2的方法. Jav

  • Java终止线程实例和stop()方法源码阅读

    了解线程 概念 线程 是程序中的执行线程.Java 虚拟机允许应用程序并发地运行多个执行线程. 线程特点 拥有状态,表示线程的状态,同一时刻中,JVM中的某个线程只有一种状态; ·NEW 尚未启动的线程(程序运行开始至今一次未启动的线程) ·RUNNABLE 可运行的线程,正在JVM中运行,但它可能在等待其他资源,如CPU. ·BLOCKED 阻塞的线程,等待某个锁允许它继续运行 ·WAITING 无限等待(再次运行依赖于让它进入该状态的线程执行某个特定操作) ·TIMED_WAITING 定时

  • Java 并发编程之线程挂起、恢复与终止

    挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的.如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件--其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁.另外,在长时间计算期间挂起线程也可能导致问题. 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: public class DeprecatedSuspendResume

  • Java中终止线程的方法详解

    Java中终止线程的方式主要有三种: 1.使用stop()方法,已被弃用.原因是:stop()是立即终止,会导致一些数据被到处理一部分就会被终止,而用户并不知道哪些数据被处理,哪些没有被处理,产生了不完整的"残疾"数据,不符合完整性,所以被废弃.So, forget it! 2.使用volatile标志位 看一个简单的例子: 首先,实现一个Runnable接口,在其中定义volatile标志位,在run()方法中使用标志位控制程序运行 public class MyRunnable i

  • 详解Java多线程编程中线程的启动、中断或终止操作

    线程启动: 1.start() 和 run()的区别说明 start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法.start()不能被重复调用. run() : run()就和普通的成员方法一样,可以被重复调用.单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程! 下面以代码来进行说明. class MyThread extends Thread{ public void run(){ ... } }; MyThread mythread = new

随机推荐