Анализ правильной остановки резьбы

Java

Как правильно остановить поток

1. Объясните принцип

принципВведение: использованиеinterruptуведомлять, а не заставлять. Каков принцип остановки потока в Java?

В Java лучший способ остановить поток — использовать прерывание, но это только информирует завершенный поток о том, что «вы должны прекратить выполнение», и завершенный поток имеет право решать (нет и когда) остановиться), что зависит как на запрашивающей, так и на остановленной стороне в соответствии с согласованным соглашением о кодировании.

Запуск задач и потоков прост. В большинстве случаев мы позволяем им идти до конца или позволяем им остановиться самостоятельно.тем не мение, иногда мы хотим завершить задачу или поток досрочно, возможно, из-за того, что пользователь отменил операцию, или службу нужно быстро закрыть, или истекло время ожидания, или произошла ошибка.

Безопасная, быстрая и надежная остановка задач и потоков — непростая задача.Java не предоставляет никакого механизма для безопасного завершения потоков. Но он обеспечивает Прерывание, которое является кооперативным механизмом., что позволяет одному потоку завершить текущую работу другого потока.

Такой совместный подход необходим, мы редко хотим, чтобы задача, поток или служба немедленно останавливались,Поскольку эта немедленная остановка оставляет общие структуры данных в несогласованном состоянии.. Вместо этого вы можете использовать совместный подход при написании задач и служб, которые сначала очищают работу, которую они выполняют в настоящее время, а затем завершают работу, когда им нужно остановиться. Это обеспечивает лучшую гибкость,Потому что код самой задачи лучше знает, как сделать очистку, чем код, сделавший запрос на отмену..

Проблемы, связанные с окончанием жизненного цикла, усложняют задачи, службы, а также разработку и реализацию программ, что является очень важным элементом разработки программ, который часто упускается из виду. Одно из основных различий между программным обеспечением с хорошим поведением и программным обеспечением, которое едва работает, заключается в том, что программное обеспечение с хорошим поведением очень хорошо справляется со сбоями, завершением работы и отменами.

В этой главе будут показаны различные механизмы реализации отмены и прерывания, а также способы написания задач и служб, чтобы они могли отвечать на запросы отмены..

2. Лучшие практики: как правильно остановить нити

2.1 Когда потоки обычно останавливаются

1. Метод run() завершен
2. Возникает исключение, которое не перехватывается потоком.
После остановки потока занятые ресурсы будут возвращены JVM.

2.2 Правильный способ остановки: прерывание

2.2.1 Как нормально остановиться

package stopthreads;

/**
 * 描述: run()方法内没有sleep()和wait()方法时,停止线程。
 */

public class RightStopThreadWithoutSleep implements Runnable{

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

    }
    @Override
    public void run() {
        int num = 0;
        while (num <= Integer.MAX_VALUE / 2){
            if (!Thread.currentThread().isInterrupted() && num % 10000 == 0) {
                System.out.println(num + "是10000的倍数");
            }
            num++;
        }
        System.out.println("任务结束了。");
    }
}

Уведомление: thread.interrupt();Нить не может быть вынуждена быть прервана, и должно быть прервано сотрудничество потока.
То есть: вам нужно добавить следующий код в дочерний поток:!Thread.currentThread().isInterrupted()

2.2.2 Как остановить поток может быть заблокирован

package stopthreads;

import static java.lang.Thread.*;

public class RightStopThreadWithSleep {
    
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            while (num <= 300){
                if (num % 100 == 0 && !currentThread().isInterrupted()){
                    System.out.println(num + "是100的整数倍");
                }
                num++;
            }
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }

}

Результат выглядит следующим образом:

2.2.3 Если поток блокируется после каждой итерации

package stopthreads;

import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;

/**
 * 描述:如果在执行过程中,每次循环都会调用sleep()或wait()等方法,那么...
 */
public class rightStopThreadWithSleepEveryLoop {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (num <= 10000){
                    if (num % 100 == 0 && !currentThread().isInterrupted()){
                        System.out.println(num + "是100的整数倍");
                    }
                    num++;
                    sleep(10);
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }

}

Проблема с попыткой поймать внутри while:

package stopthreads;

import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;

public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;

                while (num <= 10000){
                    if (num % 100 == 0 && !currentThread().isInterrupted()){
                        System.out.println(num + "是100的整数倍");
                    }
                    num++;
                    try {
                        sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }

}

Меняем позицию try-catch, результат совсем другой:

Уведомление:Даже добавив:&& !currentThread().isInterrupted()Все равно никакого эффекта!
причина:При разработке класса Thread после прерывания вызова sleep() маркер прерывания будет автоматически очищен!

2.3 Два передовых метода реальной разработки

2.3.1 Передовой опыт 1. Предпочтение: прерывание доставки (генерация исключения в подписи метода)

Сначала добавим небольшую интерлюдию:неправильно обрабатывать исключения,В вызываемом методе прямо поместитеInterruptExceptionПерехватить его, что эквивалентно проглатыванию исключения в методе нижнего уровня, так что вызов верхнего уровня не может воспринять исключение.
Правильный путь должен быть:Бросьте исключение,Реальная обработка исключения должна быть вызвана функцией, которая его вызывает.
Код ошибки выглядит следующим образом:

package stopthreads;

/**
 * 描述:  catch了InterruptionException之后的优先选择:在方法签名中抛出异常。
 * 那么,在run()中就会强制try-catch。
 */
public class RightWayStopThreadInProduction implements Runnable {

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

    @Override
    public void run() {
        while(true){
            System.out.println("go");
            throwInMethod();
        }
    }

    private void throwInMethod() {

        /**
         * 错误做法:这样做相当于就把异常给吞了,导致上层的调用无法感知到有异常
         * 正确做法应该是,抛出异常,而异常的真正处理,应该叫个调用它的那个函数。
         */
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

Исключение обработки ошибок предотвращает остановку потока:


Правильный подход: сгенерировать исключение, а реальную обработку исключения следует передать функции, которая его вызывает.
Низкоуровневый метод выдает исключение, а у вызывающего есть только Surround с try/catch.

Правильный код выглядит следующим образом:

package stopthreads;

import static java.lang.Thread.sleep;

/**
 * 描述:  catch了InterruptionException之后的优先选择:在方法签名中抛出异常。
 * 那么,在run()中就会强制try-catch。
 */
public class RightWayStopThreadInProduction implements Runnable {

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

    @Override
    public void run() {
        while(true){
            System.out.println("go");
            throwInMethod();
        }
    }

    private void throwInMethod() throws InterruptedException {

        /**
         * 错误做法:这样做相当于就把异常给吞了,导致上层的调用无法感知到有异常
         * 正确做法应该是,抛出异常,而异常的真正处理,应该叫个调用它的那个函数。
         */
       /* try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

       sleep(2000);

    }
}

Суммировать:

2.3.2 Передовая практика 2: Нежелательная или невозможная доставка: возобновить прерывание (снова вручную возобновить прерывание)

Вы можете попытаться поймать в низкоуровневых методах, но обязательно добавьтеThread.currentThread().interrupt();

package stopthreads;

import static java.lang.Thread.sleep;

public class RightWayStopThreadInProduction2 implements Runnable{

    @Override
    public void run() {

        while(true){
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("线程中断");
                break;
            }
            reInterrupt();
        }

    }

    private void reInterrupt() {

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

    }

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

результат:

Резюме: Что делать, если вы не можете вызвать прерывание?

Если вы не хотите или не можете доставитьInterruptedException(Например, при использовании метода run этот метод не разрешенthrows InterruptedException), тогда вы должны выбрать вызов в предложении catchThread.currentThread() interrupt()Чтобы восстановить настройку состояния прерывания, чтобы последующие выполнения все еще могли проверить, что прерывание только что произошло. Конкретный код показан выше.Здесь поток прерывается во время сна, и прерывание захватывается перехватом, а состояние прерывания сбрасывается, чтобы состояние прерывания можно было обнаружить в следующем цикле и нормально выйти.

Прерывания не должны быть замаскированы

2.4 Преимущества правильной остановки

3. Неправильный способ остановить поток

3.1 Первая ошибка остановки: устаревшие методы остановки, приостановки и возобновления

Использование stop() для остановки потока приведет к тому, что поток внезапно остановится наполовину, и нет возможности завершить работу базовой единицы (компания в коде), что приведет к грязным данным (некоторые компании получают больше оборудования). и меньше оборудования).

package threadcoreknowledge.createthreads.stopthreads;

/**
 * 描述:     错误的停止方法:用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(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();
    }


}

Есть и ложная теория:Даже если используется stop(), он не будет снят, а блокировка монитора (монитора) приведет к зависанию программы. У официалов есть четкое описание, стоп(), монитор (монитор) который будет выпущен.

3.2 Ошибка 2: используйте volatile для установки бита логического флага

кажется возможным

package threadcoreknowledge.stopthreads.volatiledemo;

/**
 * 描述:     演示用volatile的局限:part1 看似可行
 */
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;
    }
}

почему нет

storage.put(num); заблокирован и не может войти в новый уровень цикла while() для оценки, а значение !Canceled не может быть оценено

Демонстрация кода: java

package threadcoreknowledge.createthreads.wrongway.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = new Producer(storage);
        Thread thread = new Thread(producer);
        thread.start();
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(storage.take()+"被消费");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了");
        /**
         *  一旦消费不需要更多数据了,我们应该让生产者也停下来,
         *  但是实际情况,在 storage.put(num);处被阻塞了,无法进入新的一层while()循环中判断,!Canceled 的值也就无法判断
         */
        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 {
            //canceled为true,则无法进入
            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;
    }
}

Результат: Программа не завершается.

程序并没有结束

сделать ремонт

использоватьinterrupt:Демонстрация кода:

package threadcoreknowledge.createthreads.wrongway.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class WrongWayVolatileFixed  {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        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(storage.take()+"被消费");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了");
        /**
         *  一旦消费不需要更多数据了,我们应该让生产者也停下来,
         *  但是实际情况,在 storage.put(num);处被阻塞了,无法进入新的一层while()循环中判断,!Canceled 的值也就无法判断
         */
        producerThread.interrupt();

    }
    class Producer implements Runnable{

        BlockingQueue storage;

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

        @Override
        public void run() {
            int num = 0;
            try {
                //canceled为true,则无法进入
                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;
        }
    }
}

Результат: Программа завершается нормально.

程序正常结束

Суммировать:

总结

4. Остановить анализ исходного кода важной функции потока

4.1 Анализ исходного кода прерывания()

interrupt()源码分析

4.2 Анализ соответствующих методов оценки прерывания

判断中断的相关方法

4.2.1 static boolean interrupted()

Исходный код выглядит следующим образом:

  /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

Этот метод будетперечислитьprivate native boolean isInterrupted(boolean ClearInterrupted);и перейти в «истинный»("true" означает, очищать ли текущее состояние.) Чтобы судить о состоянии потока,и вернуть истину/ложь(Возврат true означает, что поток был прерван, а возврат false означает, что поток продолжается). И после возврата статус прерывания потока напрямую устанавливается в false. Другими словами, прямая очистка состояния прерывания потока — единственный способ очистить состояние прерывания потока. (так какprivate native boolean isInterrupted(boolean ClearInterrupted);имеютnativeМодифицированный, поэтому его нельзя назвать)

4.2.2 boolean isInterruted()

Как и статическое логическое значение interrupted();, он вернет прерванное состояние текущего потока, но метод isInterrpted() не очистит прерванное состояние.

4.2.3 Объект действия Thread.interrupted()

Объект Thread.interrupted() на самом деле является потоком, который его вызвал. Неважно, кто делает вызов, будь то вызов Thread или вызов экземпляра Thread (потока), метод interrupted() не имеет значения, ему важно только, в каком потоке он находится и в какой поток он возвращается. поток статус прерывания и очистить его после возврата.

Небольшое упражнение: подумайте о выводе следующей программы.

package threadcoreknowledge.createthreads.stopthreads;

public class RightWayInterrupted {

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

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                }
            }
        });
        // 启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());  //true
        //获取中断标志并重置
        System.out.println("isInterrupted: " + threadOne.interrupted()); // 会清除中断标志位 false
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted());  //两次清除 true
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted()); //ture
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

Результаты приведены ниже:

运行结果

5. Общие вопросы на собеседовании

5.1 Как правильно остановить поток?

Это можно объяснить с помощью следующих трех аспектов:

  1. Принцип: используйте прерывание для запроса остановки потока вместо форсирования, преимуществом является безопасность.
  2. Трехстороннее сотрудничество: чтобы остановить поток, запрашивающая сторона, остановленная сторона и вызываемый подметод должны сотрудничать друг с другом:
    А. В качестве остановленной стороны: Проверяйте сигнал прерывания в каждом цикле или в соответствующее время и обрабатывайте сигнал прерывания, когда может быть выдано InterrupedException;
    б) Запрашивающая сторона: отправить сигнал прерывания;
    C. Вызываемый приоритет находится на уровне методабросать, вместо того, чтобы ловить исключение InterrupedException, если исключение не может быть выброшено, вы также можете снова установить состояние прерывания в try-catch;
  3. Наконец, давайте поговорим о неправильном методе: от остановки/приостановки отказались, а volatile boolean не может быть обработано. Управление долгосрочной блокировкой

5.2 Как бороться с непрерываемой блокировкой (например, ReentrantLock.lock() при захвате блокировок или невозможности реагировать на прерывания во время ввода-вывода сокета, как остановить поток?)

Если поток заблокирован из-за вызова wait(), sleep() или join(), вы можете прервать поток и разбудить его, создав InterruptedException. Но для блокировки, которая не может ответить на InterruptedException,К сожалению, универсального решения не существует.Но мы можем использовать определенные другие методы, которые могут реагировать на прерывания,Например: ReentrantLock.lockInterruptably(), например: закрытие сокета и немедленный возврат потока для достижения цели.