深度解析线程的正确停止方法

news2024/12/27 16:01:01

一、解惑

1. 什么情况下,线程需要被停止?

线程和任务被创建和启动之后,大部分情况下都是自然运行到结束的,自然停止,但有些情况会需要用到停止线程,如:

  • 用户主动取消

  • 服务被快速关闭

  • 运行出错或超时情况下等线程都需要被停止

这些情况都需要主动来停止线程,想让线程安全可靠停止下来并不容易,Java语言没有一种机制来安全正确地停止线程,但是它提供了interrupt,这是一种协作机制。

2. 如何正确停止线程?

可以使用interrupt来通知中断,但它不是强制停止。

通俗理解:就是用一个线程来通知另一个线程让它停止工作,在Java中,如果想停止一个线程,能做的最多能做的就是告诉一个线程,你该中断了,而被中断的线程本身拥有决定权,它不仅能决定何时去响应这个中断,何时去停止,还拥有最高决定权,就是停不停止,也就是说,如果被停止线程不想被中断,那我们对此无能为力,根本没有能力做到强行停止。但是在开发过程中,开发的各个部门和小组都遵守良好的规范的话,是可以都把代码处理成可以响应interrupt中断来停止的,但这仅仅是一个规范,不是一种强制。

3. 我们是程序的控制者,凭什么我们没有控制线程停止的权利?

其实大多数时候我们想停止一个线程,都会至少让它运行到结束,比如说,即便我们关机的时候,也会在关机的时候做很多的收尾工作,结束一些进程、线程,保存一些状态,那么线程也是一样的,由于我们想中断的线程,可能不是我们开发的,对这个线程执行的业务逻辑根本就不熟悉,如果想让它停止,其实是希望它完成一系列的保存工作,交接工作,再停止,而不是立刻停止,让它陷入一种混乱的状态。所以,被停止的那个线程,对自己的业务逻辑是最熟悉的,而发出停止信号的线程,它对别人的业务逻辑很可能是不了解的,所以java语言设计的时候就把线程停止的权力和步骤交给了被停止线程本身,这就是停止线程的核心,而不是强制停止。

二. 停止线程的实践

1. 普通情况的线程中断

(1)直接使用 interrupt 方法,线程未停止

/**
 * 描述:run方法没有 sleep 和 wait方法
 * */
public class RightWayStopThreadWithOutSleep implements Runnable{
    @Override
    public void run() {
        int num = 0;
        while (num <= Integer.MAX_VALUE/2){
            if (num%10000 == 0){
                System.out.println(num+"是能被10000整除的数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithOutSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

打印结果:

你会发现,没有任何效果,我们使用 interrupt 想把这个线程中断,但它似乎根本就没理会我们,这个线程想不想停止,取决于它本身,所以我们需要对它进行改变,去响应 interrupt 的中断,这样便能停止线程。

(2)增加中断响应 !Thread.currentThread().isInterrupted()

当线程被中断时,即Thread.currentThread().isInterrupted()等于 true时,!Thread.currentThread().isInterrupted()等于false,线程被停止。

/**
 * 描述:run方法没有 sleep 和 wait方法
 * */
public class RightWayStopThreadWithOutSleep implements Runnable{
    @Override
    public void run() {
        int num = 0;
        while (!Thread.currentThread().isInterrupted() && num < Integer.MAX_VALUE/2){
            if (num%10000 == 0){
                System.out.println(num+"是能被10000整除的数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithOutSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

打印结果:

看打印结果中最后的数值与(1)中的打印结果相比,明显线程未执行完就被停止了,interrupt()方法中断有效。

2. 阻塞情况下的线程中断

代码演示:

/**
 * 描述:带sleep的中断线程方法
 * */
public class RightWayStopThreadWithSleep {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (!Thread.currentThread().isInterrupted() && num < 300){
                    if (num % 100 == 0){
                        System.out.println(num+"是能被100整除的数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt(); // 睡眠 500 毫秒后执行 interrupt,线程应在执行到了 Thread.sleep(1000);这个阶段
    }
}

打印结果:

发现报异常,回顾一下,当我们在写sleep()方法时,代码要求我们try-catch这个异常,然后打印的结果也是catch到了,即java.lang.InterruptedException: sleep interrupted,为什么会报异常呢,是这样的,当线程在休眠状态下,如果收到这个中断信号,线程便会响应这个中断,而响应这个中断的方式非常特殊,就是抛出这个异常,于是我们就在 catch 中打印了这个异常sleep interrupted,就是在sleep过程中被打断了。

所以当我们程序带有sleep,或者能让线程阻塞的方法,并且有可能被中断的时候,需要注意处理 InterruptedException 这个异常,我们可以放在 catch中处理,这样在线程进入阻塞过程中,依然可以响应这个中断并进行处理。

3. 线程每次迭代都阻塞

代码演示:

/**
 * 描述:带sleep的中断线程方法
 * */
public class RightWayStopThreadWithSleepEveryLoop {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (num <= 10000){
                    if (num % 100 == 0){
                        System.out.println(num+"是能被100整除的数");
                    }
                    num++;
                    Thread.sleep(10); // slepp方法放在循环里
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

打印结果:

当线程的阻塞方法是在循环中时,就不再需要 Thread.currentThread().isInterrupted() 判断线程是否被中断了,这是因为在整个循环过程中,代码的大部分运行时间都是消耗在了Thread.sleep(10) 中的,所以极有可能是在sleep时接收到interrupt,自然会抛出InterruptedException 异常,不需要在代码中加入 Thread.currentThread().isInterrupted()判断检查是否已中断。

4. 基于第3步的代码:如果While里面放 try / catch,会导致中断失效

/**
 * 描述:如果While里面放try / catch,会导致中断失效
 * */
public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            while (num <= 10000){
                if (num % 100 == 0){
                    System.out.println(num+"是能被100整除的数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

打印结果:

我们发现,执行 interrupt 之后,明明已经catchInterruptedException异常,但在线程并没有停止,反而继续执行,这是为什么呢?因为抛出异常后被catch住了,但是循环并没有跳出,不满足跳出循环的条件,会继续执行while循环。

那是不是加上在while()里加上&& !Thread.currentThread().isInterrupted(),在线程中断后,下一次循环开始时判断一下线程是否已被中断就可以了呢?来试一试吧。

/**
 * 描述:如果While里面放try / catch,会导致中断失效
 * */
public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            //while()加一个条件:!Thread.currentThread().isInterrupted()
            while (num <= 10000 && !Thread.currentThread().isInterrupted()){
                if (num % 100 == 0){
                    System.out.println(num+"是能被100整除的数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

线程依然没有停止!!!

原因是java在设计sleep()函数的时候,有这样一个理念,当它一旦响应中断,于是便会把isInterrupted()这个标记位给清除,所以在上面代码while后续检查过程中检查不到任何被中断过的迹象,导致程序不能退出。

如何解决呢?下面有两种方案。

4.1 传递中断

有时候在run()方法中不会一次性把所有业务都写在该方法中,我们可能会调用其他子方法。假设被调用的子方法在某些代码环节可能需要处理InterruptedException 异常,这时通常有两种方法:一是try/catch,二是在方法签名上直接抛出这个异常 throws xxxException

下面先来演示一下非常不好的try/catch,并说一下为什么不好。

4.1.1 举一个使用try-catch的例子:

代码过程是:调用一个通过try/catch处理异常的方法,方法内容是睡眠2秒,启动子线程后,主线程睡眠 1秒后发起中断请求,确保子线程是在throwInMethod方法里的sleep时响应中断。

/**
 * 描述: 最佳实践:catch了interruptedException之后的优先选择:在方法签名中抛出异常
 * 那么在run()方法就会强制要求try/catch
 * */
public class RightWayStopThreadInProd implements Runnable{
    @Override
    public void run() {
        while(true) {
            System.out.println("go");
            throwInMethod();
        }
    }

    private void throwInMethod() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new
                RightWayStopThreadInProd());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

打印结果:

try/catch不好的原因如下

上图明显异常已经打印了,但是不好的是,如果线程继续运行,在茫茫控制台信息中是很难注意到这个异常的,这就导致很难去处理它,所以实际上当有一个线程来打断我们时,我们没有处理好,而且把这个异常给忽略掉了,在实际生产环境中,我们就不一定能感知到这个异常。假设throwInMethod()方法是其他人员写的,我们只是负责调用的话,那遗憾的事情就发生了,别人想中断我们,但我们没有响应,而且可能还毫不知情,因为我们是负责写run()方法的内容,throwInMethod()方法是其他小伙伴负责的,我们并不了解里面的业务逻辑,只是简简单单地调用它,最后的责任却是在我们这里,因为其他线程想中断我们,我们却没有响应中断。

throwInMethod()方法中直接把中断给吞了,什么叫吞了呢?就是在throwInMethod()方法休眠的过程中有一个中断过来,但是它没有做什么处理,只是把它打印出来,没有上报给调用它的方法(run方法),它要做的应该是上报给我们run方法,因为它无法做出更多中断处理,实际应交给我们调用方,交给run方法处理,去决定在调用方法这步代码有异常情况我们应该怎么处理,是该保存日志或其它操作等等,这是我们的责任,而编写throwInMethod()方法的小伙伴的责任绝不是把异常简单的打印出来,自己吞掉,应该上报给我们,把中断的这个信息传给我们。

4.1.2 方法签名上抛出这个异常 throws InterruptedException

目的是让上层方法能感知到这个异常并做出相应的处理!!!

/**
 * 描述: 最佳实践1:catch了interruptedException之后的优先选择:在方法签名中抛出异常
 * 那么在run()方法就会强制要求try/catch
 * */
public class RightWayStopThreadInProd implements Runnable{
    @Override
    public void run() {
        while(true) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                //保存日志、停止程序
                System.out.println("保存日志");
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
        Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new
                RightWayStopThreadInProd());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

打印结果:

小结: 在被调用的字方法里如果有InterruptedException 异常时,优先选择在方法上抛出异常,即传递中断:。用throws InterruptedException 标记你的方法,不采用try 语句块捕获异常,以便于该异常可以传递到顶层,让run方法可以捕获这一异常,例如:

void subTask() throws InterruptedException{
	sleep(delay);
}

由于run方法内无法抛出checked Exception,即只能用try catch,所以run这个顶层方法必须处理该异常,避免了漏掉或者被吞掉的情况,增强了代码的健壮性。

4.1.2 不想传递或者无法传递中断:那就选择恢复中断

我们上面说优先处理中断的方法是传递中断,但是在有些情况我们是无法传递的,比如说,我们是作为run()方法的编写者,在run()方法中是不允许抛出异常的,或者有时候我们确实不想在这个方法上抛出异常,就是要自己处理的话,这边也给出了一种对应的方法:恢复中断

恢复中断总体而言就是我们在获取InterruptedException的同时,应该在catch语句中再次调用Thread.currentThread().interrupt(),这样就相当于自己把中断重新设置了一遍,这样一来在后续的执行中依然能检测到刚才发生的这个中断,并且有后续的逻辑继续去处理。

代码演示:

/**
 * 描述: 最佳实践2:在catch子语句中调用Thread.currentThread.interrupt()来恢复中断状态,
 * 以便于在后续的执行中,依然能够检查到刚才发生的中断
 * 回到刚才RightWayStopThreadInProd补上中断,让它跳出
 * */
public class RightWayStopThreadInProd2 implements Runnable{
    @Override
    public void run() {
        while(true) {
            if (Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new
                RightWayStopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

打印结果:

所以:如果不想或无法传递InterruptedException(例如用run方法的时候,就不让该方法throws InterruptedException),那么应该选择在catch 子句中调用Thread.currentThread().interrupt() 来恢复设置中断状态,以便于在后续的执行依然能够检查到刚才发生了中断,正常退出。

可以响应中断的方法总结列表:

Object.wait() / wait(long) / wait(long, int)
Thread.sleep(long) / sleep(long, int)
Thread.join() / join( long) / join(long, int)
java.util.concurrent.BlockingQueue.take() / put (E)
java.util.concurrent.locks.Lock.lockInterruptibly()
java.util.concurrent.CountDownLatch.await
java.util.concurrent.CyclicBarrier.await
java.util.concurrent.Exchanger.exchange(v)
java.nio.channels.InterruptibleChannel相关方法
java.nio.channels.Selector的相关方法


unchecked Exception 和 checked Exception的概念:

java 异常体系 Throwable 分为两类:Error和Exception。

Error是代码层面无法处理的系统之类的问题;Exception分为 RuntimeException、IOException等,RuntimeException 和 Error 属于 unchecked Exception(不受检查异常),因为编译器无法对这类异常提前预测。而Exception中除了 RuntimeException 以外的其他 Exception 都属于 checked Exception(受检查异常),因为可以被编译器提前预知,并对可能出现的异常执行对应的代码处理,比如 try/catch、throws等;

image-20230525131753531


三、错误的停止方法

1. 被弃用的stop,suspend和resume方法

用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作(一个连队),会造成脏数据(有的连队多领取少领取装备)。

public class StopThread implements Runnable {

    @Override
    public void run() {
        //模拟指挥军队:一共有5个连队,每个连队10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取
        for (int i = 0; i < 5; i++) {
            System.out.println("连队" + i + "开始领取武器");
            for (int j = 0; j < 10; j++) {
                System.out.println(j);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("连队"+i+"已经领取完毕");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();
    }
}

打印结果:

上面可以看到,stop太强制,连队1的人员还没有领取完就停止了,也就是说stop会导致原来的逻辑没完整做完就停止了。

2. 用volatile设置boolean标记位

(1) 部分场景可行

public class WrongWayVolatile implements Runnable {

    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + "是能被100整除的数");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(5000);
        r.canceled = true;
    }
}

打印结果:

这种情况下通过volitile来中断线程是可行的。

(2) 不可行的场景

代码说明:下面的例子,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费

// 主类:
public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take()+"被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");

        //一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况生产者还是会处于阻塞
        producer.canceled=true;
        System.out.println(producer.canceled);
    }
}
// 生产者:
class Producer implements Runnable {

    public volatile boolean canceled = false;

    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }


    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍数,被放到仓库中了。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者结束运行");
        }
    }
}
// 消费者:
class Consumer {

    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}

打印结果:

我们发现volatile设置为了true,但是生产者并没有停下来。

为什么说用volatile停止线程不够全面?

  • 这种做法是错误的,或者说是不够全面的,在某些情况下虽然可用,但是某些情况下有严重问题。
  • 此方法错误的原因在于,如果我们遇到了线程长时间阻塞(这是一种很常见的情况,例如生产者消费者模式中就存在这样的情况),就没办法及时唤醒它,或者永远都无法唤醒该线程,而 interrupt设计之初就是把wait等长期阻塞作为一种特殊情况考虑在内了,我们应该用 interrupt来停止线程。

(3) 改进(2)中的代码

interrupt中断来修复刚才的无尽等待问题

// main函数:
public class WrongWayVolatileFixed {

    public static void main(String[] args) throws InterruptedException {

        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = body.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");
        producerThread.interrupt();
        System.out.println(producer.isCanceled);
    }
// 生产者:
class Producer implements Runnable {

        BlockingQueue storage;

        public Producer(BlockingQueue storage) {
            this.storage = storage;
        }
        @Override
        public void run() {
            int num = 0;
            try {
                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        storage.put(num);
                        System.out.println(num + "是100的倍数,被放到仓库中了。");
                    }
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生产者结束运行");
            }
        }
    }
// 消费者:
class Consumer {

        BlockingQueue storage;

        public Consumer(BlockingQueue storage) {
            this.storage = storage;
        }

        public boolean needMoreNums() {
            if (Math.random() > 0.95) {
                return false;
            }
            return true;
        }
    }
}

打印结果:

使用interrupt,程序正常中断了。

(4) 判断是否已被中断的相关方法

  • static boolean interrupted() 判断当前线程是否被中断,调用后会把中断线程的标记直接设为false,即清除中断标记
  • boolean isInterrupted() 判断当前线程是否被中断,不会清除中断标记

注意 static boolean interrupted() 方法的目标对象是“当前线程”,而不管本方法来自于哪个实例对象:

public class RightWayInterrupted {

    public static void main(String[] args) throws InterruptedException {
        Thread threadOne = new Thread(() -> {
            for (; ; ) {
            }
        });

        // 启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //获取中断标志并重置:threadOne.interrupted()虽然是由threadOne线程发出的,但是实际执行的对象还是主线程
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted());
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

打印结果:

interrupted()是静态方法,无论是对象调用还是类调用判断的都是主线程的中断标志,所以应该都是 false。

四、总结

1. 停止线程使用 interrupt 方法

使用interrupt方法请求中断,而不是强制终止线程,是合作机制。这样,被请求中断的线程可以自主决定,处理自己的逻辑。好处是,可以保证数据安全,来得及清理,能够保证数据完整性。

2. 若让interrupt方法起效,需要多方面的配合使用

  • 请求方发出中断请求

  • 被停止方要在每次循环中或适当的时候检查中断信号,并在可能抛出InterruptException的时候处理这个信号

  • 如果线程中响应中断的是子方法,子方法被外层方法调用,有两种响应中断的最佳方案:

    • 1)传递中断、即优先在子方法层向上抛出异常,将中断信号传给run方法,在run方法层处理中断信号逻辑
    • 2)恢复中断、即子方法收到中断信号后,再次设置中断状态。

3. 如果不用interrupt,其他方法会有一定的弊端与后果

  • stop会突然停止线程,线程来不及处理剩下的数据,会导致数据不完整

  • suspend等方法会使线程挂起,不会破坏对象,抱着锁阻塞,会导致死锁

  • 用volatile设置boolean标记位无法处理长时间阻塞的情况,导致线程无法停止

如果线程阻塞是由于调用了wait(),sleep()或join()方法,你可以中断线程,通过抛出 InterruptedException异常来唤醒该线程。

4. 无法响应中断时如何停止线程,即如何处理不可中断的阻塞

需要根据不同的类调用不同的方法。

如果线程阻塞是由于调用了 wait(),sleep() 或 join() 方法,你可以中断线程,通过抛出 InterruptedException 异常来从阻塞中唤醒该线程。
但是对于不能响应InterruptedException的阻塞,很遗憾,并没有一个通用的解决方案。
但是我们可以利用特定的其它的可以响应中断的方法,比如ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。
答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。

总结就是说如果不支持响应中断,就要用特定方法来唤起,没有万能药。

文章来源:深度解析线程的正确停止方法

个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/571831.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++实现插入排序、希尔排序、冒泡排序、快速排序、选择排序】

使用C实现来插入排序、希尔排序、冒泡排序、快速排序、选择排序算法。 一、插入排序 插入排序&#xff0c;一般也被称为直接插入排序。对于少量元素的排序&#xff0c;它是一个有效的算法 。插入排序是一种最简单的排序方法&#xff0c;它的基本思想是将一个记录插入到已经排好…

2023爱分析·中国面向开发者的低代码开发平台市场厂商评估报告

01 研究范围定义 “低代码”是一种可视化的应用开发方式&#xff0c;相对于传统编写代码的“纯代码”开发方式&#xff0c;低代码开发平台可以减少代码编写量或不使用代码编写进行应用的开发。随着技术革新&#xff0c;大模型也为低代码开发平台发展指明了新方向。从开发者与开…

单片机GD32F303RCT6 (Macos环境)开发 (二十八)—— 蓝牙透传模块HC-08 Android App开发

蓝牙透传模块HC-08 Android App开发 1、App整体开发思路 a、首先要申请权限&#xff0c;采用动态申请的方式&#xff0c;用户点击确认后方可操作蓝牙。 b、搜索蓝牙&#xff0c;之前的版本用startLeScan函数搜索蓝牙&#xff0c;虽然高版本中依然可用&#xff0c;但是google已…

什么时候该停止使用Scrum?

01、TL;DR:一个团队什么时候应该停止使用Scrum? 什么时候才能超越Scrum?毕竟许多类似思想、实践等事务迟早会过时;那为什么Scrum会是个例外?此外&#xff0c;我们不是通过实践Scrum来获得报酬&#xff0c;而是在既定的约束条件下解决客户的问题&#xff0c;同时又能为组织的…

( 链表) 707. 设计链表 ——【Leetcode每日一题】

❓707. 设计链表 难度&#xff1a;中等 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则还…

ubuntu 安装ffmpeg

一、我的编译环境 ubuntu 22 ffmpeg 4.36 二、安装必要的依赖 sudo apt-get update sudo apt-get install -y \autoconf \automake \build-essential \cmake \git-core \libass-dev \libfreetype6-dev \libsdl2-dev \libtool \libva-dev \libvdpau-dev \libvorbis-dev \lib…

160743-62-4,DMG PEG2000,1,2-二肉豆蔻酰-rac-甘油-3-甲氧基聚乙二醇2000

DMG PEG2000&#xff0c;DMG-mPEG2000&#xff0c;1,2-二肉豆蔻酰-rac-甘油-3-甲氧基聚乙二醇2000 Product structure&#xff1a; Product specifications&#xff1a; 1.CAS No&#xff1a;160743-62-4 2.Molecular formula&#xff1a; C34H66O 3.Molecular weight&#xff…

Ubuntu18.04 dash to dock启动器安装教程

1.安装主题工具&#xff1a;GNOME Tweaks sudo apt-get update sudo apt-get install gnome-tweak-tool2.手动安装dash-to-dock插件 Dash-to-dock不支持3.32以上版本的gnome&#xff0c;git clone dash to dock的仓库 yeatsyeats-virtual-machine:~/Tools$ git clone https:/…

Axure教程—垂直方向多色图(中继器)

本文将教大家如何用AXURE制作动态垂直方向多色图 一、效果介绍 如图&#xff1a; 预览地址&#xff1a;https://9fxtte.axshare.com 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87822547?spm1001.2014.3001.5503 二、功能介绍 简单填写中继…

mfc100.dll丢失如何解决?修复mfc100.dll的方法分享

mfc100.dll是Microsoft Visual C 2010中的一个动态链接库文件。如果该文件丢失&#xff0c;将会导致某些应用程序无法正常运行。在本文中&#xff0c;我们将探讨关于mfc100.dll丢失的问题&#xff0c;以及如何解决它。 一.什么是mfc100.dll mfc100.dll是Microsoft Visual C 20…

头羊部落亮相第26届北京餐食会

第26届AIFE2023亚洲&#xff08;北京&#xff09;国际食品饮料暨餐饮食材展览会&#xff08;简称&#xff1a;BCFE北京餐食会) 于2023年5月23-25日在北京中国国际展览中心火热召开。顺应时代发展下的餐饮新潮流&#xff0c;北京餐食会首次聚焦预制菜市场&#xff0c;为彰显预制…

Web安全:代码执行漏洞 测试.

Web安全&#xff1a;代码执行漏洞 测试 攻击者可以通过构造恶意输入来欺骗应用程序执行恶意代码。这种漏洞通常出现在应用程序中使用动态语言(如 PHP、Python、Ruby 等)编写的代码中&#xff0c;因为这些语言允许在运行时动态执行代码。攻击者可以通过构造特定的输入来欺骗应用…

分布式事务的21种武器 - 5

在分布式系统中&#xff0c;事务的处理分布在不同组件、服务中&#xff0c;因此分布式事务的ACID保障面临着一些特殊难点。本系列文章介绍了21种分布式事务设计模式&#xff0c;并分析其实现原理和优缺点&#xff0c;在面对具体分布式事务问题时&#xff0c;可以选择合适的模式…

经典组件知识(zookeeper,kafka,ngix)

关于zookeeper的具体介绍 优化的点可以在于zookeeper吗&#xff1f; 如何安装使用&#xff1f; #include <zookeeper/zookeeper.h> 1、先配置java环境JDK&#xff0c;因为需要用java编译&#xff1b; 2、下载zk源码&#xff0c;解压&#xff1b; 3、重命名配置文件zoo_sa…

5000 字手把手实战|Kubernetes+极狐GitLab CI,获得极致 CI/CD 体验

目录 极狐GitLab CI K8s 架构解析 极狐GitLab CI 流程图 流程详解 极狐GitLab CI K8s 架构优点 开启极狐GitLab CI K8s 实战 环境准备 记录注册信息 获取极狐GitLab Runner 绑定 docker.sock 配置缓存 安装极狐GitLab Runner 集成 CI 定义文件 注意事项 配置…

Python实战基础11-函数

1 函数的创建于调用 1.1 创建一个函数 创建函数也称为定义函数&#xff0c;定义函数的格式如下&#xff1a; def functionname([parameterlist]): [comments] [functionbody] 参数说明&#xff1a; functionname&#xff1a;函数名称&#xff0c;在调用函数时使用。 paramete…

用echarts绘制的柱状图、折柱结合图,源码文末免费拿!

文章目录 Apache EchartsNPM 安装 ECharts在线定制 ECharts使用 Echarts 绘制基础柱状图绘制带背景的柱状图绘制带背景的柱状图绘制多条柱状图绘制条形柱状图绘制带标记的柱状图绘制折线图和柱状图绘制多轴折线图和柱状图源码地址 Apache Echarts 本文中的所有代码&#xff0c…

剑指offer(C++)-JZ46:把数字翻译成字符串(算法-动态规划)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 有一种将字母编码成数字的方式&#xff1a;a->1, b->2, ... , z->26。 现在给一串数字&#xf…

如何在Angular应用程序中插入自定义 CSS?这里有答案!

Kendo UI for Angular是专用于Angular开发的专业级Angular组件&#xff0c;telerik致力于提供纯粹的高性能Angular UI组件&#xff0c;无需任何jQuery依赖关系。 Kendo UI R1 2023正式版下载(Q技术交流&#xff1a;726377843&#xff09; 为什么需要在 Angular 应用程序中插入…

兼容性测试点和注意项,建议收藏

一&#xff1a;兼容性测试的概念&#xff1a;就是验证开发出来的程序在特定的运行环境中与特定的软件、硬件或数据相组合是否能正常运行、有无异常的测试过程。 二&#xff1a;兼容性测试的分类&#xff1a; &#xff08;1&#xff09;浏览器兼容性测试 指的是在浏览器上检查…