Занимайтесь своими делами, обработка исключений потоков

Java

Раньше, когда я использовал потоки для выполнения задач, я всегда игнорировал обработку исключений потоков, пока недавно не прочитал книгу.

Тест исключения потока

  • Класс задачи: Task.java

    public class Task implements Runnable {
        private int i;
    
        public Task(int i) {
            this.i = i;
        }
    
        @Override
        public void run() {
            if (i == 5) {
                //System.out.println("throw exception");
                throw new IllegalArgumentException();
            }
            System.out.println(i);
        }
    }
    

    Если i==5, будет выброшено исключение

  • Класс тестирования потока: TaskTest.java

    public class TestTask {
        public static void main(String[] args) {
            int i = 0;
            while (true) {
                if (i == 10) break;
                try {
                    new Thread(new Task(i++)).start();
                } catch (Exception e) {
                    System.out.println("catch exception...");
                }
            }
        }
    }
    

    Попытка поймать сгенерированное исключение с помощью try-catch

  • Результаты теста

    Connected to the target VM, address: '127.0.0.1:64551', transport: 'socket'
    0
    1
    2
    3
    4
    6
    7
    8
    9
    Exception in thread "pool-1-thread-1" java.lang.IllegalArgumentException
        at com.h2t.study.thread.Task.run(Task.java:21)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
    

    Исключение не перехватывается, но оно выводится на консоль и не влияет на выполнение последующих задач. Эмммм почему это?Если вы не можете поймать исключение, вы не знаете, что программа неверна.В это время есть задача, которую невозможно проверить нормально.Это крайне важно. Взгляните на класс Thread, там есть метод с именем dispatchUncaughtException, который действует, как следует из названия, отправляет неперехваченные исключения и находит следующий код:Thread#dispatchUncaughtException

    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    

    find использование не может найти, где вызывается метод, потому что этот метод вызывается только JVMThread#getUncaughtExceptionHandler:Получите класс реализации интерфейса UncaughtExceptionHandler

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
    

UncaughtExceptionHandler — это интерфейс, определенный в Thread.В классе Thread uncaughtExceptionHandler по умолчанию имеет значение null, поэтому этот метод вернет группу, то есть класс ThreadGroup, который реализует интерфейс UncaughtExceptionHandler.UncaughtExceptionHandler#uncaughtException:Реализация метода uncaughtException класса ThreadGroup

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}

Поскольку в классе Thread нет назначения группе [parent] и defaultUncaughtExceptionHandler [Thread.getDefaultUncaughtExceptionHandler], он войдет в последний уровень условий, выведет исключение на консоль и ничего не сделает с исключением. Вся цепочка вызовов обработчиков исключений выглядит следующим образом:

Во-первых, оцените, является ли обработчик исключений по умолчанию [defaultUncaughtExceptionHandler] нулевым, затем оцените, является ли обработчик исключений группы потоков [group] нулевым, и оцените, является ли пользовательский обработчик исключений [uncaughtExceptionHandler] нулевым, если все нулевые, он находится под контролем исключение для настольной печати

Обработка исключений потока

Проанализировав исходный код, мы знаем, что если вы хотите обрабатывать исключения во время выполнения задачи, один из них — сделать ThreadGroup не нулевым, а другой — сделать значение переменной типа UncaughtExceptionHandler не нулевым.

  • Обработчик исключений: ExceptionHandler.java

    private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("异常捕获到了:" + e);
        }
    }
    
  • Установить обработчик исключений по умолчанию

    Thread.setDefaultUncaughtExceptionHandler((t, e) -> System.out.println("异常捕获到  了: " + e));
    int i = 0;
    while (true) {
      if (i == 10) break;
      Thread thread = new Thread(new Task(i++));
      thread.start();
    }
    

    распечатать результат:

    0
    2
    1
    3
    9
    6
    7
    4
    异常捕获到了:java.lang.IllegalArgumentException
    8
    

    Установив исключение по умолчанию, вам не нужно устанавливать его один раз для каждого потока.

  • Установить собственный обработчик исключений

    Thread t = new Thread(new Task(i++));
    t.setUncaughtExceptionHandler(new ExceptionHandler());
    

    распечатать результат:

    0
    2
    4
    异常捕获到了:java.lang.IllegalArgumentException
    6
    1
    3
    7
    9
    8
    
  • Установить обработчик исключений группы потоков

    MyThreadGroup myThreadGroup = new MyThreadGroup("测试线程线程组");
    Thread t = new Thread(myThreadGroup, new Task(i++))
    

    Пользовательская группа потоков: MyThreadGroup.java

    private static class MyThreadGroup extends ThreadGroup {
        public MyThreadGroup(String name) {
            super(name);
        }
    
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("捕获到异常了:" + e);
        }
    }
    

    распечатать результат:

    1
    2
    0
    4
    3
    6
    7
    8
    9
    捕获到异常了:java.lang.IllegalArgumentException
    

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

Обработка исключений пула потоков

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

  • Обработчик исключений по умолчанию

     Thread.setDefaultUncaughtExceptionHandler(new  ExceptionHandler());
     ExecutorService es = Executors.newCachedThreadPool();
    es.execute(new Task(i++))
    
  • собственный обработчик исключений

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>());
    threadPoolExecutor.setThreadFactory(new MyThreadFactory());
    threadPoolExecutor.execute(new Task(i++));
    

    Пользовательский класс фабрики: MyThreadFactory.java

    private static class MyThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
          Thread t = new Thread();
          //自定义UncaughtExceptionHandler
          t.setUncaughtExceptionHandler(new ExceptionHandler());
          return t;
       }
    }
    

Принципы проектирования, зачем захватывать самой нитью

Из философии дизайна JVM «потокиНезависимостьИсполняемый фрагмент кода, проблема потока должна решаться самим потоком, а не делегироваться вовне». граница кода потока и не должна обрабатываться другими потоками за пределами метода потока.

Поток выполняет вызываемую задачу

Вышеприведенный случай, когда поток выполняет задачу типа Runnable, Как мы все знаем, есть также тип задачи Callable с возвращаемым значением. Код теста: TestTask.java

public class TestTask {
    public static void main(String[] args) {
        int i = 0;
        while (true) {
            if (i == 10) break;
            FutureTask<Integer> task = new FutureTask<>(new CallableTask(i++));
            Thread thread = new Thread(task);
            thread.setUncaughtExceptionHandler(new ExceptionHandler());
            thread.start();
        }
    }
    
    private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("异常捕获到了:" + e);
        }
    }
}

распечатать результат:

Disconnected from the target VM, address: '127.0.0.1:64936', transport: 'socket'
0
1
2
3
4
6
7
8
9

Результат наблюдения, исключение не перехвачено, настройка метода thread.setUncaughtExceptionHandler(new ExceptionHandler()) недействительна, эммммм, почему это, спрашивается, почему 100 000 дочерних элементов. Просмотрите метод запуска FutureTask,Будущая задача # запустить:

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

FutureTask#setException:

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
      //将异常设置给outcome变量
      outcome = t;
      //设置任务的状态为EXCEPTIONAL
      UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
      finishCompletion();
    }
}

См. код перехвата, когда задача выполнения перехватывает исключение, она устанавливает результат обработки задачи равным нулю и вызывает метод setException для обработки перехваченного исключения, поскольку setUncaughtExceptionHandler обрабатывает только необработанные исключения, FutureTask имеет исключение перехвачено, поэтому вызов setUncaughtExceptionHandler для перехвата исключения недействителен Вызовите метод get по результату выполнения задачи:

int i = 0;
while (true) {
    if (i == 10) break;
    FutureTask<Integer> task = new FutureTask<>(new CallableTask(i++));
    Thread thread = new Thread(task);
    thread.setUncaughtExceptionHandler(new ExceptionHandler());
    thread.start();
    //打印结果
    try {
    System.out.println(task.get());
    } catch (Exception e) {
      System.out.println("异常被抓住了, e: " + e);
    }
}

Результат выполнения распечатает пойманное исключение и результат выполнения:

0
1
2
3
4
异常被抓住了, e: java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException
6
7
Disconnected from the target VM, address: '127.0.0.1:50900', transport: 'socket'
8
9

Будущая задача#получить:

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
      //未完成等待任务执行完成
      s = awaitDone(false, 0L);
    return report(s);
}

FutureTask#отчет:

private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
      return (V)x;
    if (s >= CANCELLED)
      throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

Результат устанавливается как исключение в методе setException, а состояние, в котором s является состоянием, окончательно устанавливается в EXCEPTIONAL, поэтому метод выдает захваченную задачу [new ExecutionException((Throwable)x)]
Суммировать:
Исключения, создаваемые вызываемыми задачами, могут быть перехвачены в коде с помощью try-catch, новызывать только метод getбыть захваченным после

image.png

Прикреплен к предыдущим статьям: добро пожаловать, читайте, лайкайте и комментируйте

Связанный с параллелизмом
1. Почему Alibaba отключает Executors для создания пулов потоков?

Связанные с шаблоном проектирования:
1. Паттерн синглтон, ты правда правильно пишешь?
2. (режим стратегии + заводской режим + карта) переключать корпус в пакете Kill project

Связанные с JAVA8:
1. Оптимизируйте свой код с помощью Stream API
2. Уважаемый, вместо Date рекомендуется использовать LocalDateTime

Связанные с базой данных:
1. Сравнение эффективности запросов типов времени базы данных mysql datetime, bigint и timestamp.
2. Очень доволен! Наконец-то ступил на яму медленного запроса

Эффективные связанные:
1. Создайте каркас Java, чтобы унифицировать стиль структуры командного проекта.

Связанный журнал:
1. Структура журнала, выберите Logback или Log4j2?
2. Файл конфигурации Logback пишется так, а TPS увеличен в 10 раз.

Связанные с инженерией:
1. Когда вам нечего делать, пишите LRU локальный кеш
2. Redis реализует аналогичный функциональный модуль
3. Пул потоков визуального мониторинга JMX
4. Управление разрешениями [Spring Security]
5. Spring пользовательская аннотация от входа до мастерства
6. Java имитирует посадку на Youku
7. QPS такой высокий, давайте писать многоуровневый кеш
8. Java использует phantomjs для создания снимков экрана

разное:
1. Используйте попытку с ресурсами, чтобы изящно закрыть ресурсы
2. Босс, почему я должен вычитать свою зарплату, если я использую float для хранения суммы?