java基础之线程中断

线程中断

1、线程概述

1.1、线程中断的粗暴和柔性

在以前的JDK版本中,如果不想要线程继续运行,那么需要中断线程的话,那么可以直接将线程stop掉;

但是这样子在线程中工作的程序可能会收到一定的影响,比如说在批量提交数据阶段,其中的一个线程直接被中断掉,那么将会导致数据的不完整性等一些列的情况发生。所以在JDK8版本中,提供了一种更加柔性的操作方式来对线程中断做一种操作方式,只是让给线程打上标记,表示线程中断,但是当前的线程依然可以继续运行。

Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

API使用:

interrupt(): 将线程的中断标志位设置为true,不会停止线程

isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位

Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle

注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况

注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况

注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况

处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样就会导致 while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个 条件时退出。如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止

public class ThreadStopTwo {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyTask());
        try {
            thread.start();
            Thread.sleep(100);
            thread.interrupt();
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyTask implements Runnable{
        @Override
        public void run() {
            int count =0;
            while (!Thread.currentThread().isInterrupted()){
                count++;
            }
            try {
                Thread.sleep(1002);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程停止---stop,最终的count的值是:"+count);
            System.out.println("当前线程中断状态:"+Thread.currentThread().isInterrupted());
        }
    }
}

控制台打印输出:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.guang.thread.threadzhongduan.ThreadStopTwo$MyTask.run(ThreadStopTwo.java:29)
	at java.lang.Thread.run(Thread.java:748)
线程停止---stop,最终的count的值是:98599625
当前线程中断状态:false

处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样就会导致 while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个 条件时退出。如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止

所以需要改进一下:

public class ThreadStopTwoPro {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyTask());
        try {
            thread.start();
            Thread.sleep(100);
            thread.interrupt();
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyTask implements Runnable{
        @Override
        public void run() {
            int count =0;
            while (!Thread.currentThread().isInterrupted()){
                count++;
            }
            try {
                Thread.sleep(1002);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
            System.out.println("线程停止---stop,最终的count的值是:"+count);
            System.out.println("当前线程中断状态:"+Thread.currentThread().isInterrupted());
        }
    }
}

控制台输出:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.guang.thread.threadzhongduan.ThreadStopTwoPro$MyTask.run(ThreadStopTwoPro.java:29)
	at java.lang.Thread.run(Thread.java:748)
线程停止---stop,最终的count的值是:118728648
当前线程中断状态:true

sleep可以被中断抛出中断异常:sleep interrupted, 清除中断标志位 ;

wait可以被中断抛出中断异常:InterruptedException, 清除中断标志位;

粗暴型中断线程示例:

public class ThreadStop {
    public static void main(String[] args) {
        int i = 0;
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("--------->>>>hello,world<<<<<<----------------");
                if (i==2){
                    break;
                }
            }
            System.out.println("thread is running.......................");
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
            thread.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

效果:在控制台上,只能够看到一直输出的是hello,world。但是线程终止的情况下,线程是直接挂掉了,而不会再去运行thread is running

柔性中断线程示例:

public class ThreadStop2 {
    public static void main(String[] args) {
        int i = 0;
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("--------->>>>hello,world<<<<<<----------------");
                if (i==2){
                    break;
                }
            }
            System.out.println("thread is running.......................");
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

效果:在控制台上,只能够看到一直输出的是hello,world。但是线程打上终止标记的情况下,线程没有挂掉,而再去运行thread is running。

所以这里柔性的中断线程的方式并没有真正的将线程终止掉,而是能够让线程中的程序继续运行。

1.2、柔性中断线程的三个方法

Thread类中提供了线程中断的三个方法来进行操作:

  • void interrupt:给线程打上中断标记,表示的是线程中断了,开发者需要自己来对中断的线程做处理。

  • boolean isInterrupted():判断当前的线程是否有中断标记。有中断,返回true;没有中断,返回false

  • static boolean interrupted():如果线程中断,那么擦除对应的中断,返回true;如果线程没有中断,那么返回false 。

那么介绍完了上面的方法,下面来一个案例来进行描述:

public class ThreadStopOne {
    // 当前线程是主线程
    public static void main(String[] args) {
        System.out.println("当前main线程的状态是否已经终止?"+Thread.currentThread().isInterrupted()); // false
        // 打上中断标记
        Thread.currentThread().interrupt();
        // 再次来进行判断线程中断状态
        System.out.println("当前main线程的状态是否已经终止?"+Thread.currentThread().isInterrupted()); // true
        // 那么调用擦除中断标记的方法,这个方法在返回true之后,还会将线程中断的标记给清楚掉!
        boolean interrupted = Thread.interrupted(); // true
        if (interrupted){
            System.out.println("已经将线程中断标记给清楚掉了,那么来看一下");
            System.out.println("当前main线程的状态是否已经终止?"+Thread.currentThread().isInterrupted()); // false
        }
        // 在这里再次检查一下线程的中断状态,这个时候中断标记已经为false了,那么再次调用interrupted的时候,返回的是false
        boolean interruptedTmp = Thread.interrupted(); // false
        if (!interruptedTmp){
            System.out.println("此时线程状态没有中断标记"+interruptedTmp); // false
        }
    }
}

1.3、柔性中断作用

interrupt方法用于中断线程。调用该方法的线程的状态为将被置为中断状态,而不是真正的去将线程进行中断,当我们的程序检测到了当前的线程是中断状态之后,我们可以来进行对应的自定义操作方式。比如说批量提交数据到数据库阶段,如果检查到了,那么直接让事务进行回滚等等操作,这种是更加不错的选择方式。

注意线程中断仅仅是置线程的中断状态位,而不会停止线程。需要用户自己去监视线程的状态为并做处理。(这里体现出来对应的柔性的处理方式)

好处:1、在线程池条件下,线程复用;2、创建线程和终止线程操作会浪费资源

比如说线程执行任务需要1ms,而创建线程可能需要100ms,那么这样子来频繁操作,会浪费系统资源。这也是需要线程池的原因。

1.4、线程中断抛出异常的方法

在线程中断标记期间,如果当前线程处于sleep,wait,join阶段,那么处于这些状态的线程会立即抛出异常,然后清除打断标记,然后throw interrupted exception,那么此时的线程状态依然是未中断状态,但是正是因为抛出了异常,才会导致让开发者认识到是因为中断中断而导致的错误。

对应代码如下所示:

public class ThreadInteruptExceptionTest {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("当前的线程是否中断?"+Thread.currentThread().isInterrupted());
                Thread.sleep(1000);
                System.out.println("hello,world");
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("当前的线程是否中断?"+Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        thread.interrupt();
    }
}

可以看到控制台输出信息:

当前的线程是否中断?true
当前的线程是否中断?false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.guang.threadstop.ThreadInteruptExceptionTest.lambda$main$0(ThreadInteruptExceptionTest.java:13)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

当前的线程在设置中断标记状态后,抛出异常,并将当前的线程的中断标记给清楚掉了,所以这就是为什么返回的是false了。

那么我们可以在catch代码中发现了线程是中断引起的,那么就可以来对线程执行的任务进行处理或者是重置当前线程中断状态。

其实这里更加柔性的线程中断,对比这上面的三个方法来说,也就是让其处于不同的线程状态而已。比如说sleep处于time_waitting阶段,wait让其处于waitting阶段,而Join也是让其处于被调度的状态。

但是另外的一种状态:Blocked状态,不会让其停止,因为可能还在进行同步锁操作后者是IO操作。那么这里的几个阶段我们都来测试一下:

案例一

首先先来一个简单的简单的案例,看一下如果线程中断了,程序是否还真的可以继续运行。

public class DemoOne {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (;;){
                System.out.println("hello,interupt");
            }
        });
        thread.start();
        thread.interrupt();
    }
}

可以看到控制台一直在输出,而没有停止,说明了中断标记不会引起线程的中断。

案例二

但是如果想要在线程中断的时候,跳出需要进行执行的逻辑,那么对上面的线程进行改进:

public class DemoOne {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (;;){
                System.out.println("hello,interupt");
                // 加上这行代码和不加的区别:仅仅只是添加了线程中断标记而已,但是并不影响线程的运行
                if (Thread.currentThread().isInterrupted()){
                    break;
                }
            }
        });
        thread.start();
        thread.interrupt();
    }
}

从这里可以看到线程如果被中断了,在线程中的程序检测到了之后,会进入到线程中执行break,从而线程执行结束。

这里是核心思想:给线程打上中断标记,不会让线程终止掉。

线程标记的小结

只是让我们开发人员检测到了标记状态之后,如何对已经中断的线程来进行操作而已。

/**
 * 结论,研究一下发现,调用t1.interrupt()后立即调用t1.isInterrupted(),有可能在t1还未完成清除打断标记并抛异常的时候就查看打断标记,此时仍然为true,
 * 如果要得到稳定的false,即重置打断标记后的结果,应该在t1.interrupt()后, sleep一会,给点时间,有点像jvm启动后偏向锁延迟设置的意思。
 */
@Slf4j
public class InteruptDemoOne {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
            // 可能会导致线程中断
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug(" 打断状态: {}", t1.isInterrupted());
        t1.interrupt();
        // 抛出异常之后,JVM将会重新把线程中断状态重置为false,这里居然也有为TRUE的情况
        log.debug(" 打断状态: {}", t1.isInterrupted());
    }
}

如果要得到稳定的false,即重置打断标记后的结果,应该在t1.interrupt()后, sleep一会,给点时间,有点像jvm启动后偏向锁延迟设置的意思。

控制台输出:

11:04:07.415 [main] DEBUG com.yg.edu.interup.InteruptDemoOne -  打断状态: false
11:04:07.419 [main] DEBUG com.yg.edu.interup.InteruptDemoOne -  打断状态: true
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.yg.edu.interup.InteruptDemoOne.lambda$main$0(InteruptDemoOne.java:17)
	at java.lang.Thread.run(Thread.java:748)

看到上面的两种状态的输出情况。

3、线程中断的三种方式

1、stop简单粗暴;

2、interupt给线程设置标记;

3、flag标记位;

第一种肯定是不适用的,推荐使用第二种使用方式。

比较可取的是下面两种方式:

  • 设置标记位
public class test1 {

    public static volatile boolean exit =false;  //退出标志
    
    public static void main(String[] args) {
        Thread thread = new Thread() {
            public void run() {
                System.out.println("线程启动了");
                while (!exit) {
                    try {
                        Thread.sleep(1000*10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程结束了");
            }
        };

        try {
            thread.start();
            // 这里不能够使用Thread.Join(),因为对于新起来的线程来说,在里面使用while循环最终是无法终止的,永远等不到停的时刻。所以使用sleep来说,比较优雅一点
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        exit = true;//5秒后更改退出标志的值,没有这段代码,线程就一直不能停止
    }
}
  • 通过interupt方法
public class Test3 {
    public static void main(String[] args) {

        Thread thread = new Thread() {
            public void run() {
                System.out.println("线程启动了");
                // 检测线程是否中断了。这里还可以来直接使用测试一下是否可以测试成功........
                while (!isInterrupted()) {
                    System.out.println(isInterrupted());//调用 interrupt 之后为true
                }
                System.out.println("线程结束了");
            }
        };
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("线程是否被中断:" + thread.isInterrupted());//true
    }
}

4、对interruptedException的处理方式

常见的两种选择方式

1、传递InterruptedException。避开这个异常通常是最明智的策略——只需把InterruptedException传递给方法调用者。传递InterruptedException的方法包括,根本不捕获该异常,或者捕获该异常,然后在执行某种简单的清理工作后再次抛出该异常。

2、恢复中断。因为在中断发生之后,抛出异常的时候,JVM已经将该线程的中断标记进行清除了。所以我们可以手动的来进行设置

try{
    Thread.sleep(2000);
}catch(interruptedException e){
    // 打上标记,然后上下文中判断,如果处于这个状态的线程应该怎么样来进行执行
	Thread.currentThread().interrupt();
}

这样子也不免是一种优雅的方式。

posted @ 2022-03-10 14:36  雩娄的木子  阅读(33)  评论(0编辑  收藏  举报