线程中断的3+2(自定义执行状态和处理不可中断的阻塞)种方式、setDaemon(守护线程)、Future的方法

Java分为两种线程:用户线程和守护线程

守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

该方法必须在启动线程前调用。 
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。

Future与线程停止相关的方法

1)isDone()
如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。 
2)cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。 
此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。 
参数: 
mayInterruptIfRunning - 如果应该中断执行此任务的线程,则为 true;否则允许正在运行的任务运行完成 
返回: 
如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true

3)isCancelled()
如果在任务正常完成前将其取消,则返回 true。 
返回: 
如果任务完成前将其取消,则返回 true
此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。 
参数: 
mayInterruptIfRunning - 如果应该中断执行此任务的线程,则为 true;否则允许正在运行的任务运行完成 
返回: 
如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true

java提供线程中断的三种方式

Thread提供的中断线程的三个方法

  • public void interrupt():如果线程处于阻塞状态,那么仅仅抛出InterruptedException异常,不会设置线程的中断状态为true;如果线程没有处于阻塞状态,仅仅设置线程的中断状态为true。
  • public static boolean Thread.interrupted():静态方法, 测试当前线程是否已经中断,如果已经调用interrupt方法则返回true,并且线程的中断状态 由该方法清除。
  • public boolean isInterrupted():测试线程是否已经中断。线程的中断状态 不受该方法的影响。

1线程处于阻塞(调用了sleep,wait,io等待等)状态,或试图执行一个进入阻塞的操作,此时调用 interrupt()方法,线程直接被中断,并抛出interruptException,同时中断状态将会被复位(由中断状态改为非中断状态)。实例如下

public class InterruputSleepThread3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                //while在try中,通过异常中断就可以退出run循环
                try {
                    while (true) {
                        //当前线程处于阻塞状态,异常必须捕捉处理,无法往外抛出
                        TimeUnit.SECONDS.sleep(2);
                    }
                } catch (InterruptedException e) {
                    System.out.println("Interruted When Sleep");
                    boolean interrupt = this.isInterrupted();
                    //中断状态被复位
                    System.out.println("interrupt:"+interrupt);
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        //中断处于阻塞状态的线程
        t1.interrupt();

        /**
         * 输出结果:
           Interruted When Sleep
           interrupt:false
         */
    }
}

2先调用interrupt,将设置线程的中断状态,后调用Interruped()方法,此方法不抛出异常,同时中断状态被复位.

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run(){
                while(true){
                    System.out.println("未被中断");
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();

        /**
         * 输出结果(无限执行):
             未被中断
             未被中断
             未被中断
             ......
         */
    }
}

虽然我们调用了interrupt方法,但线程t1并未被中断,因为处于非阻塞状态的线程需要我们手动进行中断检测并结束程序,改进后代码如下:

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run(){
                while(true){
                    //判断当前线程是否被中断
                    if (this.isInterrupted()){
                        System.out.println("线程中断");
                        break;
                    }
                }

                System.out.println("已跳出循环,线程中断!");
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();

        /**
         * 输出结果:
            线程中断
            已跳出循环,线程中断!
         */
    }
}

是的,我们在代码中使用了实例方法isInterrupted判断线程是否已被中断,如果被中断将跳出循环以此结束线程,注意非阻塞状态调用interrupt()并不会导致中断状态重置。综合所述,可以简单总结一下中断两种情况,

  • 当线程处于阻塞状态或者试图执行一个阻塞操作时,我们可以使用实例方法interrupt()进行线程中断,执行中断操作后将会抛出interruptException异常(该异常必须捕捉无法向外抛出)并将中断状态复位,
  • 当线程处于运行状态时,我们也可调用实例方法interrupt()进行线程中断,但同时必须手动判断中断状态,并编写中断线程的代码(其实就是结束run方法体的代码)。

有时我们在编码时可能需要兼顾以上两种情况,那么就可以如下编写:

public void run(){
    try {
    //判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位
    // 使得线程一直处于阻塞状态,这样只要该线程调用了interrupt方法,就会终止线程,抛出异常
    while (!Thread.interrupted()) {
        TimeUnit.SECONDS.sleep(2);
    }
    } catch (InterruptedException e) {

    }
}

3针对任务尚未启动的场景:调用Future.cancel(boolean mayInterruptIfRunning)方法
ExecutorService exec = new CatchedThreadPool();
Future future = exec.submit(runnbale);
future.cancel(true);

试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。 

上面的三种方式,其实都是使用java平台提供给我们的方法,实际上我们自己也可以通过设定线程的状态值自定义线程的终止策略

线程中自定义状态值来中断线程

直接上代码:

public class CancelRunnable implements Runnable{

    // 线程运行状态,注意,这里一定声明为valatile,修改canceled值后,线程能迅速感知
    public volatile boolean canceled = false;

    // 通过修改状态值来停止线程
    public void cancel(){
        canceled = true;
        System.out.println("cancel thread");
    }

    @Override
    public void run() {
        while(!canceled){
            System.out.println("running");
        }
    }

    public static void main(String[] args) {
        CancelRunnable cancelRunnable = new CancelRunnable();
        Thread thread = new Thread(cancelRunnable);
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cancelRunnable.cancel();
        /**
         * 结果如下:
         * running
         * running
         * .......
         * running
         * cancel thread
         */
    }
}

上面这种方式有一点需要特别说明:如果run方法中的内容是阻塞的,那么一定不可以使用上面的方式,因为如果while循环中是一种阻塞的方法并且正处在阻塞的状态中,此时虽然你调用了cancel方法修改了canceled的状态,但是线程是无法读取到修改后的状态值的,也就无法停止线程。

线程中断之不可中断的阻塞

怎么理解不可中断的阻塞呢?

  • 可中断的阻塞:如TimeUnit.SECONDS.sleep(2),这种阻塞遇到线程的interrupt方法就会抛出InterruptedException
  • 不可中断阻塞:如javaIO包中的IO阻塞,selector的异步IO,或者是synchronized导致的锁等待等,这些阻塞不会对interrupt方法做出任何响应,是不会中断的。

不可中断阻塞,具体的实例如BufferedReader.readLine()方法,针对这种阻塞我们可以先调用io流的close方法,然后再调用thread的interrupt方法来中断线程。下面是一个实例:

public class ReaderThread extends Thread{

    BufferedReader reader ;
    InputStreamReader stream;

    public ReaderThread(BufferedReader reader,InputStreamReader stream){
        this.reader=reader;
        this.stream=stream;
    }

    public  void  run(){
        try {
            // 控制台攻击输入三行,run方法才算执行完毕
            String x1 = reader.readLine();
            System.out.println("第一次输入:"+x1);
            String x2 = reader.readLine();
            System.out.println("第二次输入:"+x2);
            String x3 = reader.readLine();
            System.out.println("第三次输入:"+x3);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 重写interrupt方法,先关闭流(即先关闭不可中断的阻塞),然后再中断线程
     */
    public void interrupt(){
        try {
            // 这里的关闭流操作很必要,否则的话线程是不能中断的
            stream.close();
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            super.interrupt();
        }
    }

    public static void main(String[] args) {
        InputStreamReader inputStream = new InputStreamReader(System.in);
        BufferedReader bufferedReader = new BufferedReader(inputStream);
        Thread thread = new ReaderThread(bufferedReader, inputStream);
        thread.start();
        try {
            // 2秒后只要有输入就会报异常然后停止程序
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

中断与synchronized

事实上线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用,也就是对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么它获得这把锁继续执行,要么它就保存等待,即使调用中断线程的方法,也不会生效。演示代码如下

/**
 * Created by zejian on 2017/6/2.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 */
public class SynchronizedBlocked implements Runnable{

    public synchronized void f() {
        System.out.println("Trying to call f()");
        while(true) // Never releases lock
            Thread.yield();
    }

    /**
     * 在构造器中创建新线程并启动获取对象锁
     */
    public SynchronizedBlocked() {
        //该线程已持有当前实例锁
        new Thread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }
    public void run() {
        //中断判断
        while (true) {
            if (Thread.interrupted()) {
                System.out.println("中断线程!!");
                break;
            } else {
                f();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlocked sync = new SynchronizedBlocked();
        Thread t = new Thread(sync);
        //启动后调用f()方法,无法获取当前实例锁处于等待状态
        t.start();
        TimeUnit.SECONDS.sleep(1);
        //中断线程,无法生效
        t.interrupt();
    }
}

我们在SynchronizedBlocked构造函数中创建一个新线程并启动获取调用f()获取到当前实例锁,由于SynchronizedBlocked自身也是线程,启动后在其run方法中也调用了f(),但由于对象锁被其他线程占用,导致t线程只能等到锁,此时我们调用了t.interrupt();但并不能中断线程。

你可能感兴趣的:(java多线程)