zoukankan      html  css  js  c++  java
  • (转)Java结束线程的三种方法

    背景:面试过程中问到结束线程的方法和线程池shutdown shutdownnow区别以及底层的实现,当时答的并不好。

    Java结束线程的三种方法

    线程属于一次性消耗品,在执行完run()方法之后线程便会正常结束了,线程结束后便会销毁,不能再次start,只能重新建立新的线程对象,但有时run()方法是永远不会结束的。例如在程序中使用线程进行Socket监听请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。当需要结束线程时,如何退出线程呢?

    有三种方法可以结束线程:

    • 1.设置退出标志,使线程正常退出,也就是当run()方法完成后线程终止

    • 2.使用interrupt()方法中断线程

    • 3.使用stop方法强行终止线程(不推荐使用,Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 这些终止线程运行的方法已经被废弃,使用它们是极端不安全的!)

    前两种方法都可以实现线程的正常退出;第3种方法相当于电脑断电关机一样,是不安全的方法

    1.使用退出标志终止线程 
    一般run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出,代码示例:

    public class ThreadSafe extends Thread {
        public volatile boolean exit = false; 
            public void run() { 
            while (!exit){
                //do something
            }
        } 
    }

    定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值.

    2.使用interrupt()方法中断当前线程 
    使用interrupt()方法来中断线程有两种情况:

    1.线程处于阻塞状态,如使用了sleep,同步锁的wait,socket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的, 一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。

    代码示例:

    public class ThreadSafe extends Thread {
        public void run() { 
            while (true){
                try{
                        Thread.sleep(5*1000);//阻塞5妙
                    }catch(InterruptedException e){
                        e.printStackTrace();
                        break;//捕获到异常之后,执行break跳出循环。
                    }
            }
        } 
    }

    2.线程未处于阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。 
    代码示例:

    public class ThreadSafe extends Thread {
        public void run() { 
            while (!isInterrupted()){
                //do something, but no throw InterruptedException
            }
        } 
    }

    为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑:

    代码示例:

    public class ThreadSafe extends Thread {
        public void run() { 
            while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
                try{
                    Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
                }catch(InterruptedException e){
                    e.printStackTrace();
                    break;//捕获到异常之后,执行break跳出循环。
                }
            }
        } 
    }

    3.使用stop方法终止线程 
    程序中可以直接使用thread.stop()来强行终止线程,但是stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用stop方法来终止线程。

     

    interrupt、interrupted 、isInterrupted 区别

     

    interrupt、interrupted 、isInterrupted 区别

    1、interrupt 

    interrupt方法用于中断线程。调用该方法的线程的状态为将被置为"中断"状态。
    注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
        public void interrupt() {
            if (this != Thread.currentThread())
                checkAccess();
    
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();           // Just to set the interrupt flag
                    b.interrupt(this);
                    return;
                }
            }
            interrupt0();
        }
    
        private native void interrupt0();

    interrupt 设置中断状态。

    2、interrupted 和 isInterrupted

    首先看一下该方法的实现:
        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
    该方法就是直接调用当前线程的isInterrupted(true)方法。
    静态方法,返回当前的线程的状态,并会清楚之前的状态。
    然后再来看一下 isInterrupted的实现:
        public boolean isInterrupted() {
            return isInterrupted(false);
        }

    只会返回当前线程的状态,并不会清除。 

    底层都是调用一个本地方法:

        /**
         * Tests if some Thread has been interrupted.  The interrupted state
         * is reset or not based on the value of ClearInterrupted that is
         * passed.
         */
        private native boolean isInterrupted(boolean ClearInterrupted);
    原来这是一个本地方法,看不到源码。不过没关系,通过参数名我们就能知道,这个参数代表是否要清除状态位。

    如果这个参数为true,说明返回线程的状态位后,要清掉原来的状态位(恢复成原来情况)。这个参数为false,就是直接返回线程的状态位。

    这两个方法很好区分,只有当前线程才能清除自己的中断位(对应interrupted()方法)

    interrupted 和 isInterrupted

    这两个方法有两个主要区别:
    1. interrupted 是作用于当前线程,isInterrupted 是作用于调用该方法的线程对象所对应的线程。(线程对象对应的线程不一定是当前运行的线程。例如我们可以在A线程中去调用B线程对象的isInterrupted方法。)
    2. 这两个方法最终都会调用同一个方法,只不过参数一个是true,一个是false;

    是不是这样理解呢,
    Thread.currentThread().interrupted(); 这个用于清除中断状态,这样下次调用Thread.interrupted()方法时就会一直返回为false,因为中断标志已经被恢复了。
    而调用isInterrupted 只是简单的查询中断状态,不会对状态进行修改。

    interrupt()是用来设置中断状态的。返回true说明中断状态被设置了而不是被清除了。我们调用sleep、wait等此类可中断(throw InterruptedException)方法时,一旦方法抛出InterruptedException,当前调用该方法的线程的中断状态就会被jvm自动清除了,就是说我们调用该线程的isInterrupted 方法时是返回false。如果你想保持中断状态,可以再次调用interrupt方法设置中断状态。这样做的原因是,java的中断并不是真正的中断线程,而只设置标志位(中断位)来通知用户。如果你捕获到中断异常,说明当前线程已经被中断,不需要继续保持中断位。
    interrupted是静态方法,返回的是当前线程的中断状态。例如,如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。

    https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%B9%B6%E5%8F%91.md#interrupted

    ps:里面有interrupted()相关的详细使用例子。

    调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

    如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

    Future<?> future = executorService.submit(() -> {
        // ..
    });
    future.cancel(true);

    threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和区别

    threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和区别

    最近在看并发编程,在使用到ThreadPoolExecutor时,对它的三个关闭方法(shutdown()、shutdownNow()、awaitTermination())产生了兴趣,同时又感到迷惑。查了些资料,自己写了测试代码,总算有了个比较清晰的认识。下面一起来看看这三个方法:

    shutdown()

    将线程池状态置为SHUTDOWN,并不会立即停止:

    • 停止接收外部submit的任务
    • 内部正在跑的任务和队列里等待的任务,会执行完
    • 等到第二步完成后,才真正停止

    shutdownNow()

    线程池状态置为STOP。企图立即停止,事实上不一定:

    跟shutdown()一样,

    • 先停止接收外部提交的任务
    • 忽略队列里等待的任务
    • 尝试将正在跑的任务interrupt中断
    • 返回未执行的任务列表

    它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。

    但是大多数时候是能立即退出的

    awaitTermination(long timeOut, TimeUnit unit)

    当前线程阻塞,直到

    • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完
    • 或者等超时时间到
    • 或者线程被中断,抛出InterruptedException
    • 然后返回true(shutdown请求后所有任务执行完毕)或false(已超时)

    实验发现,shuntdown()和awaitTermination()效果差不多,方法执行之后,都要等到提交的任务全部执行完才停。

    shutdown()和shutdownNow()的区别

    从字面意思就能理解,shutdownNow()能立即停止线程池,正在跑的和正在等待的任务都停下了。这样做立即生效,但是风险也比较大;
    shutdown()只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。

    Between client threads and thread pool there is a queue of tasks. When your application shuts down, you must take care of two things: what is happening with queued tasks and how already running tasks are behaving (more on that later). Surprisingly many developers are not shutting down thread pool properly or consciously. There are two techniques: either let all queued tasks to execute (shutdown()) or drop them (shutdownNow()) - it totally depends on your use case.

    shutdown()和awaitTermination()的区别
    shutdown()后,不能再提交新的任务进去;但是awaitTermination()后,可以继续提交。
    awaitTermination()是阻塞的,返回结果是线程池是否已停止(true/false);shutdown()不阻塞。

    总结

    优雅的关闭,用shutdown()
    想立马关闭,并得到未执行任务列表,用shutdownNow()
    优雅的关闭,并允许关闭声明后新任务能提交,用awaitTermination()
    关闭功能 【从强到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()

    线程池的状态

    Java多线程线程池(4)--线程池的五种状态

    线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated

    线程池各个状态切换框架图:

    image

  • 相关阅读:
    【Qt开发】QTableWidget设置根据内容调整列宽和行高
    【Qt开发】Qt在QLabel(QWidget)鼠标绘制直线和矩形框
    【Qt开发】Qt在QLabel(QWidget)鼠标绘制直线和矩形框
    【Qt开发】Qt5.7串口开发
    【Qt开发】Qt5.7串口开发
    Oracle监听配置、数据库实例配置等
    SqlMapConfig.xml中的setting属性 Ibatis mybatis
    Hibernate的like用法
    eclipse逆向生成实体类注解方式或者xml方式
    struts2 ValueStack详解,页面获取值el表达式、ognl表达式
  • 原文地址:https://www.cnblogs.com/lixuwu/p/10766125.html
Copyright © 2011-2022 走看看