Как изящно остановить поток?

Java

в предыдущей статьеi-code.online — «Параллельное программирование — основы потоков»Мы представили создание и завершение потоков и разобрались в деталях с точки зрения исходного кода Теперь, если кто-то спросит вас «Как изящно остановить поток?», что вы должны ответить Ni? Можете ли вы ответить Ни идеально?

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

Что происходит, когда поток принудительно останавливается?


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

  • Для Java самый правильный способ остановить поток — использоватьinterrupt. ноinterrupt Он только играет роль уведомления об остановленном потоке. Для остановленного потока у него есть полная автономия, он может выбрать остановку немедленно, через некоторое время или не останавливаться вообще. Многие учащиеся могут задаться вопросом, раз это так, то в чем смысл этого существования, на самом деле, дляJavaС точки зрения ожидания, что программы будут потоками управления, которые могут уведомлять друг друга и взаимодействовать друг с другом.

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

прерывание останавливает поток

  • оinterruptМы не будем слишком подробно останавливаться на использовании , вы можете видетьi-code.online — «Параллельное программирование — основы потоков»Суть введения в статье состоит в том, чтобы вызвать нить черезisInterrupt()Затем метод оценивает сигнал прерывания, когда поток обнаруживает, что онtrueВ это время это означает, что сигнал завершения получен, и нам нужно выполнить соответствующую обработку в это время.

  • Давайте напишем простой пример, чтобы увидеть
 Thread thread = new Thread(() -> {
            while (true) {
                //判断当前线程是否中断,
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted());
                	//跳出循环,结束线程
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "线程正在执行...");

            }
        }, "interrupt-1");
        //启动线程 1
        thread.start();

        //创建 interrupt-2 线程
        new Thread(() -> {
            int i = 0;
            while (i <20){
                System.out.println(Thread.currentThread().getName()+"线程正在执行...");
                if (i == 8){
                    System.out.println("设置线程中断...." );
                    //通知线程1 设置中断通知
                    thread.interrupt();

                }
                i ++;
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"interrupt-2").start();

Приведенный выше код относительно прост. Мы создаем два потока. В первом потоке мы выполняем обнаружение сигнала прерывания. При получении запроса на прерывание цикл завершается, и поток завершается естественным образом. Во втором потоке мы моделируем, когда выполнение достигаетi==8Когда поток уведомлений завершается, в этом случае мы видим естественное завершение программы.

Вот мысль: когда вsleepМожет ли поток почувствовать сигнал прерывания?

  • Для этого особого случая мы можем немного изменить приведенный выше код, чтобы убедиться, что мы добавляем код потока 1 вsleepВ то же время, сделайте время сна дольше, так что поток 1 все еще находится в состоянии сна, когда потока 2 уведомляется, и соблюдайте, можно ли ощущаться ли сигнал прерывания в это время.
        //创建 interrupt-1 线程

        Thread thread = new Thread(() -> {
            while (true) {
                //判断当前线程是否中断,
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted());
                    Thread.interrupted(); // //对线程进行复位,由 true 变成 false
                    System.out.println("经过 Thread.interrupted() 复位后,中断标记:" + Thread.currentThread().isInterrupted());

                    //再次判断是否中断,如果是则退出线程
                    if (Thread.currentThread().isInterrupted()) {
                        break;
                    }
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "线程正在执行...");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "interrupt-1");

Выполняем модифицированный код и находим, что еслиsleep,wait Когда метод, который может заставить поток войти в блокировку, переводит поток в спящий режим, а спящий поток прерывается, тогда поток может почувствовать сигнал прерывания и выдаст ошибку.InterruptedException исключение, одновременно очистить сигнал прерывания, установить бит флага прерывания вfalse. Таким образом, не нужно беспокоиться о том, что поток не почувствует прерывание во время длительного сна, потому что даже если поток все еще спит, он все равно может ответить на уведомление о прерывании и выдать исключение.

Для остановки потока самый элегантный способ - передатьinterruptспособ достижения, о его подробной статье вы можете прочитать в предыдущей статье, напримерInterruptedException, снова прервите настройку, чтобы программа могла продолжить завершение операции. Но дляinterruptДостижение завершения потока редко используется в реальной разработке, и многие могут предпочесть другой способ, через бит флага.

метод остановки с изменяемым битом флага

  • оvolatileСуть бита-маркера заключается в его функции видимости.Давайте рассмотрим ее с помощью простого кода:

/**
 * @ulr: i-code.online
 * @author: zhoucx
 * @time: 2020/9/25 14:45
 */
public class MarkThreadTest {

    //定义标记为 使用 volatile 修饰
    private static volatile  boolean mark = false;

    @Test
    public void markTest(){
        new Thread(() -> {
            //判断标记位来确定是否继续进行
            while (!mark){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程执行内容中...");
            }
        }).start();

        System.out.println("这是主线程走起...");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //10秒后将标记为设置 true 对线程可见。用volatile 修饰
        mark = true;
        System.out.println("标记位修改为:"+mark);
    }
}

Вышеупомянутый код также был в нашей предыдущей статье. Он не будет объясняться здесь. Это установленный флаг, чтобы сделать поток видимым, а затем завершить программу. Здесь нам нужно обсудить, что использованиеvolatileЭто действительно не проблема.Приведенные выше сценарии не являются проблемой, но они используются в некоторых специальных сценариях.volatileКогда есть проблема, также необходимо обратить внимание!

Сценарии, в которых бит флажка изменчивой модификации неприменим

  • Здесь мы используем шаблон производства/потребления для реализацииDemo

/**
 * @url: i-code.online
 * @author: zhoucx
 * @time: 2020/10/12 10:46
 */
public class Producter implements Runnable {

    //标记是否需要产生数字
    public static volatile boolean mark = true;

    BlockingQueue<Integer> numQueue;

    public Producter(BlockingQueue numQueue){
        this.numQueue = numQueue;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num < 100000 && mark){
                //生产数字,加入到队列中
                if (num % 50 == 0 ){
                    System.out.println(num + " 是50的倍数,加入队列");
                    numQueue.put(num);
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("生产者运行结束....");
        }
    }
}

Сначала объявите производителяProducer,пройти черезvolatile Начальное значение тегаtrueлогическое значениеmarkчтобы остановить нить. пока вrun() метод,whileПостановление о сужденииnumЭто меньше, чем100000а также markотмечен.whileРешение в теле циклаnum Если оно кратно 50, введите егоnumQueueна складе,numQueueпамять для общения между производителями и потребителями, когдаnumбольше, чем100000 Или когда уведомят об остановке, он выскочитwhileзациклить и выполнитьfinallyБлок утверждений, сообщающий всем, что «производитель закончил работу»


/**
 * @url: i-code.online
 * @author: zhoucx
 * @time: 2020/10/12 11:03
 */
public class Consumer implements Runnable{

    BlockingQueue numQueue;

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

    @Override
    public void run() {

        try {
            while (Math.random() < 0.97){
                //进行消费
                System.out.println(numQueue.take()+"被消费了...");;
                TimeUnit.MILLISECONDS.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("消费者执行结束...");
            Producter.mark = false;
            System.out.println("Producter.mark = "+Producter.mark);
        }

    }
}


И для потребителейConsumer, который использует тот же репозиторий, что и производительnumQueue,существуетrun()В этом методе мы определяем, следует ли продолжать потребление, оценивая размер случайного числа. Только что производитель произвел для использования потребителем несколько чисел, кратных 50. Условие оценки того, продолжает ли потребитель использовать число, состоит в том, чтобы генерирует случайное число и сравнивает его с 0,97, что больше, чем 0,97. 0,97 перестает использовать числа.


/**
 * @url: i-code.online
 * @author: zhoucx
 * @time: 2020/10/12 11:08
 */
public class Mian {


    public static void main(String[] args) {
        BlockingQueue queue = new LinkedBlockingQueue(10);

        Producter producter = new Producter(queue);
        Consumer consumer = new Consumer(queue);

        Thread thread = new Thread(producter,"producter-Thread");
        thread.start();
        new Thread(consumer,"COnsumer-Thread").start();

    }
}

Основная функция очень проста, создать публичный складqueueДлина 10, затем передается в два потока, а затем запускаются два потока.Когда мы начинаем, мы должны обратить внимание, что есть сон, когда мы потребляем100миллисекунд, то этот публичный склад неизбежно будет заполнен производителями и заблокирован в ожидании потребления.

Когда потребителю больше не нужны данные, для флага canceled будет установлено значение true.Теоретически производитель выпрыгнет из цикла while и распечатает «конец операции производителя».

Однако результат не такой, как мы себе представляли, хотяProducter.markустановлен вfalse, но производитель все еще не останавливается, потому что в этом случае производитель выполняетnumQueue.put(num)При блокировке нет возможности войти в следующую петлю до тех пор, пока она не будет разбужена.Producter.markзначение , поэтому в этом случае используйтеvolatile Нет способа остановить производителя, наоборот, если вы используетеinterrupt оператор прерывания, даже если производитель находится в заблокированном состоянии, он все равно может чувствовать сигнал прерывания и реагировать на него.

Суммировать



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

В нашем обычном развитии мы зависим от ситуации, это не значит, что нужно использовать `interrupt`, `volatile` вообще можно использовать, но это требует от нас точного понимания сценариев.

Эта статья опубликована AnonyStar и может быть воспроизведена с указанием первоисточника.
Восхищайтесь «Искусством элегантного кодирования» Верьте в то, что практика делает совершенным, усердно работайте, чтобы изменить свою жизнь
Добро пожаловать в публичный аккаунт WeChat: шорткод Yunqi для получения более качественных статей
Для получения дополнительных статей обратите внимание на блог автора:Юнци Шорткод