Рубрики
Без рубрики

Обработка исключений Java – Исключение IllegalMonitorStateException

Продвигаясь по нашей углубленной серии обработки исключений Java, сегодня мы перейдем к незаконному Mon… Помеченный java.

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

В этой статье мы рассмотрим исключение IllegalMonitorStateException более подробно, начиная с того, где оно находится в общей иерархии исключений Java . Мы также рассмотрим пример функционального кода, который иллюстрирует, как может быть реализована многопоточность и одновременное ожидание, и как IllegalMonitorStateExceptions могут появиться, если в такой реализации есть некоторые небольшие недостатки. Давайте разберемся”!

Технический Обзор

Все ошибки Java реализуют java.lang. Бросаемый интерфейс, или расширяются из другого унаследованного в нем класса. Полная иерархия исключений этой ошибки выглядит следующим образом:

Полный Пример Кода

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

// Main.java
package io.airbrake;

import io.airbrake.utility.Logging;

public class Main {

    static Main main = null;

    public static void main(String[] args)
    {
        main = new Main();
    }

    private Main()
    {
        Logging.lineSeparator("DUAL THREADING");
        DualThreadingTest();

        Logging.lineSeparator("DUAL THREADING w/ OWNERSHIP");
        DualThreadingWithOwnershipTest();
    }

    private void DualThreadingTest() {
        try {
            // Create manager.
            ThreadingManager manager = new ThreadingManager();

            // Create runners and add to manager.
            manager.addRunner(new Runner("primary"));
            manager.addRunner(new Runner("secondary"));

            // Create threads, set runners, and add to manager.
            manager.addThread(new Thread(manager.getRunner("primary"), "primary"));
            manager.addThread(new Thread(manager.getRunner("secondary"), "secondary"));

            // Start threads.
            manager.getThread("primary").start();
            manager.getThread("secondary").start();
        } catch (IllegalMonitorStateException exception) {
            // Output expected IllegalMonitorStateExceptions.
            Logging.log(exception);
        } catch (Exception exception) {
            // Output unexpected Exceptions.
            Logging.log(exception, false);
        }
    }

    private void DualThreadingWithOwnershipTest() {
        try
        {
            // Create manager.
            ThreadingManager manager = new ThreadingManager();

            // Create runners and add to manager.
            manager.addRunner(new Runner("primary"));
            manager.addRunner(new Runner("secondary"));

            // Create threads, set runners, and add to manager.
            manager.addThread(new Thread(manager.getRunner("primary"), "primary"));
            manager.addThread(new Thread(manager.getRunner("secondary"), "secondary"));

            // Set runner thread ownership.
            manager.getRunner("primary").setThread(manager.getThread("primary"));
            manager.getRunner("secondary").setThread(manager.getThread("secondary"));

            // Start threads.
            manager.getThread("primary").start();
            manager.getThread("secondary").start();
        } catch (IllegalMonitorStateException exception) {
            // Output expected IllegalMonitorStateExceptions.
            Logging.log(exception);
        } catch (Exception exception) {
            // Output unexpected Exceptions.
            Logging.log(exception, false);
        }
    }
}

// ThreadingManager.java
package io.airbrake;

import java.util.ArrayList;
import java.util.Objects;

public class ThreadingManager {
    private ArrayList runners = new ArrayList<>();
    private ArrayList threads = new ArrayList<>();

    ThreadingManager() { }

    void addRunner(Runner runner) {
        this.runners.add(runner);
    }

    void addThread(Thread thread) {
        threads.add(thread);
    }

    public Runner getRunner(int index) {
        return runners.get(index);
    }

    Runner getRunner(String name) {
        for (Runner runner : runners) {
            if (Objects.equals(runner.getName(), name)) {
                return runner;
            }
        }
        return null;
    }

    public ArrayList getRunners() {
        return runners;
    }

    public Thread getThread(int index) {
        return threads.get(index);
    }

    Thread getThread(String name) {
        for (Thread thread : threads) {
            if (Objects.equals(thread.getName(), name)) {
                return thread;
            }
        }
        return null;
    }

    public ArrayList getThreads() {
        return threads;
    }

    public void setRunners(ArrayList runners) {
        this.runners = runners;
    }

    public void setThreads(ArrayList threads) {
        this.threads = threads;
    }
}

// Runner.java
package io.airbrake;

import io.airbrake.utility.Logging;

public class Runner implements Runnable
{
    private String name;
    private Thread thread;

    Runner(String name) {
        setName(name);
    }

    Runner(String name, Thread thread) {
        setName(name);
    }

    String getName() {
        return this.name;
    }

    private Thread getThread() {
        return thread;
    }

    private void setName(String name) {
        this.name = name;
    }

    void setThread(Thread thread) {
        this.thread = thread;
    }

    public void run()
    {
        try
        {
            // Check for ownership thread.
            if (getThread() == null) {
                Logging.log(String.format("Waiting for thread %s in runner %s.", "Main", getName()));
                // If no thread, wait for main thread.
                Main.main.wait();
            } else {
                synchronized (getThread()) {
                    Logging.log(String.format("Waiting for thread %s in runner %s.", getThread().getName(), getName()));
                    // If thread, invoke wait().
                    getThread().wait();
                }
            }
        } catch (IllegalMonitorStateException exception) {
            // Output expected IllegalMonitorStateExceptions.
            Logging.log(exception);
        } catch (Exception exception) {
            // Output unexpected Exceptions.
            Logging.log(exception, false);
        }
    }
}

В этом примере кода также используется Logging.java класс, источник которого можно найти на GitHub .

Когда Вы Должны Его Использовать?

Как описано в официальной документации, исключение IllegalMonitorStateException может возникать, когда поток пытается дождаться монитора объекта или уведомить другие потоки, ожидающие монитора указанного объекта, когда этот поток не владеет данным монитором. Иными словами, если метод Object.wait() вызывается для объекта, который не был создан текущим потоком, будет вызвано исключение IllegalMonitorStateException . Поток может стать владельцем монитора объекта с помощью:

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

Это легче понять с помощью некоторых примеров кода, но прежде чем мы перейдем к этому, стоит кратко отметить, что использование Object.wait() и notify() методы, как правило, не являются подходящим методом для выполнения многопоточного или параллельного программирования в современной Java. Если вас интересует, как большинство разработок Java обрабатывают параллелизм, взгляните на пакет java.util.concurrent .

Цель нашего примера кода проста: создать два потока и назначить каждому свой собственный Запускаемый объект, представляющий собой интерфейс, реализующий метод run() . A Запускаемый экземпляр может быть передан новому Потоку экземпляру, и метод Runnable.run() будет автоматически выполнен потоком после его запуска.

Таким образом, мы начнем с нашего Runner класса, который реализует Управляемый интерфейс:

package io.airbrake;

import io.airbrake.utility.Logging;

public class Runner implements Runnable
{
    private String name;
    private Thread thread;

    Runner(String name) {
        setName(name);
    }

    Runner(String name, Thread thread) {
        setName(name);
    }

    String getName() {
        return this.name;
    }

    private Thread getThread() {
        return thread;
    }

    private void setName(String name) {
        this.name = name;
    }

    void setThread(Thread thread) {
        this.thread = thread;
    }

    public void run()
    {
        try
        {
            // Check for ownership thread.
            if (getThread() == null) {
                Logging.log(String.format("Waiting for thread %s in runner %s.", "Main", getName()));
                // If no thread, wait for main thread.
                Main.main.wait();
            } else {
                synchronized (getThread()) {
                    Logging.log(String.format("Waiting for thread %s in runner %s.", getThread().getName(), getName()));
                    // If thread, invoke wait().
                    getThread().wait();
                }
            }
        } catch (IllegalMonitorStateException exception) {
            // Output expected IllegalMonitorStateExceptions.
            Logging.log(exception);
        } catch (Exception exception) {
            // Output unexpected Exceptions.
            Logging.log(exception, false);
        }
    }
}

Помимо нескольких методов получения и установки, основная логика находится в методе run() . Он начинается с проверки, существует ли свойство Thread потока бегуна с помощью метода getThread() . Если нет, он явно вызывает метод Main.main.wait() , в противном случае он вызывает метод wait() связанного Потока объекта для этого Бегун экземпляр.

Класс Менеджер потоков – это просто вспомогательный класс, который упрощает создание и отслеживание Бегуны и Потоки сохраняя их в частном порядке ArrayList бегуны и ArrayList<Поток> потоки свойства. Мы также будем использовать многие вспомогательные методы, такие как get Runner(имя строки) , для извлечения конкретных экземпляров Бегуны и Потоки для целей тестирования позже:

package io.airbrake;

import java.util.ArrayList;
import java.util.Objects;

public class ThreadingManager {
    private ArrayList runners = new ArrayList<>();
    private ArrayList threads = new ArrayList<>();

    ThreadingManager() { }

    void addRunner(Runner runner) {
        this.runners.add(runner);
    }

    void addThread(Thread thread) {
        threads.add(thread);
    }

    public Runner getRunner(int index) {
        return runners.get(index);
    }

    Runner getRunner(String name) {
        for (Runner runner : runners) {
            if (Objects.equals(runner.getName(), name)) {
                return runner;
            }
        }
        return null;
    }

    public ArrayList getRunners() {
        return runners;
    }

    public Thread getThread(int index) {
        return threads.get(index);
    }

    Thread getThread(String name) {
        for (Thread thread : threads) {
            if (Objects.equals(thread.getName(), name)) {
                return thread;
            }
        }
        return null;
    }

    public ArrayList getThreads() {
        return threads;
    }

    public void setRunners(ArrayList runners) {
        this.runners = runners;
    }

    public void setThreads(ArrayList threads) {
        this.threads = threads;
    }
}

Говоря о тестировании, давайте посмотрим на наш Основной класс и первый метод тестирования, Тест с двумя потоками() :

public class Main {
    // ...

    private void DualThreadingTest() {
        try {
            // Create manager.
            ThreadingManager manager = new ThreadingManager();

            // Create runners and add to manager.
            manager.addRunner(new Runner("primary"));
            manager.addRunner(new Runner("secondary"));

            // Create threads, set runners, and add to manager.
            manager.addThread(new Thread(manager.getRunner("primary"), "primary"));
            manager.addThread(new Thread(manager.getRunner("secondary"), "secondary"));

            // Start threads.
            manager.getThread("primary").start();
            manager.getThread("secondary").start();
        } catch (IllegalMonitorStateException exception) {
            // Output expected IllegalMonitorStateExceptions.
            Logging.log(exception);
        } catch (Exception exception) {
            // Output unexpected Exceptions.
            Logging.log(exception, false);
        }
    }

    // ...
}

Как вы можете видеть, ваш тест начинается с нового Менеджера потоков экземпляра, из которого мы создаем два новых Бегуны названные первичные и вторичный , соответственно. Затем мы создаем два новых Потока с одинаковыми именами и извлекаем соответствующий Бегуны из нашего менеджера экземпляра, чтобы перейти к этим новым потокам (...) вызовы конструктора. Как упоминалось ранее, проходя Запускаемый экземпляр (например, Бегун объект) в Конструктор потока гарантирует, что метод Runner |run() выполняется, когда поток начинает выполнение. Следовательно, наш последний шаг - запустить() оба потока.

Выполнение Теста с двумя потоками() кода приводит к следующему результату, который включает в себя выбрасывание некоторых Исключение Illegalmonitorstateexception :

----------------- DUAL THREADING ------------
Waiting for thread Main in runner primary.
Waiting for thread Main in runner secondary.
[EXPECTED] java.lang.IllegalMonitorStateException
[EXPECTED] java.lang.IllegalMonitorStateException

Поскольку мы явно не сообщили нашим Runner экземплярам, какие конкретные Поток каждому экземпляру был назначен, оба они попытались вызвать метод wait() объекта Main.main , как видно из этого фрагмента из метода Runner.run() :

// ...

// Check for ownership thread.
if (getThread() == null) {
    Logging.log(String.format("Waiting for thread %s in runner %s.", "Main", getName()));
    // If no thread, wait for main thread.
    Main.main.wait();
} else {
    synchronized (getThread()) {
        Logging.log(String.format("Waiting for thread %s in runner %s.", getThread().getName(), getName()));
        // If thread, invoke wait().
        getThread().wait();
    }
}

// ...

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

Одним из решений является использование встроенной инструкции synchronized , которая определяет объект, который будет обеспечивать внутреннюю блокировку блока кода в инструкции. Вы можете увидеть пример этого в else{... } блок нашего Runner.run() метода выше. Используя оператор synchronized (getthread()) , мы передаем код в synchronized блокируем доступ к блокировке связанного потока этого Экземпляр Runner (полученный с помощью getThread() ). Стоит отметить, что обычно лучшей практикой программирования является использование локальной переменной и извлечение Поток экземпляр через get Thread() один раз, затем повторно используйте его во всем коде. Однако в этом случае использование локальной переменной в операторе synchronized может быть сложным для управления, поскольку синхронизация может происходить не по порядку (медленнее или быстрее), чем код за его пределами, который фактически создал локальную переменную. Таким образом, в этом случае безопаснее каждый раз делать явный вызов.

Чтобы проверить это поведение, у нас есть метод Двойной поток с тестом владения() , который аналогичен Тесту с двойным потоком () , за исключением случаев, когда мы явно назначаем Поток экземпляров к Исполнителю экземпляров перед запуском() вызывается для обоих Потоков :

private void DualThreadingWithOwnershipTest() {
    try
    {
        // Create manager.
        ThreadingManager manager = new ThreadingManager();

        // Create runners and add to manager.
        manager.addRunner(new Runner("primary"));
        manager.addRunner(new Runner("secondary"));

        // Create threads, set runners, and add to manager.
        manager.addThread(new Thread(manager.getRunner("primary"), "primary"));
        manager.addThread(new Thread(manager.getRunner("secondary"), "secondary"));

        // Set runner thread ownership.
        manager.getRunner("primary").setThread(manager.getThread("primary"));
        manager.getRunner("secondary").setThread(manager.getThread("secondary"));

        // Start threads.
        manager.getThread("primary").start();
        manager.getThread("secondary").start();
    } catch (IllegalMonitorStateException exception) {
        // Output expected IllegalMonitorStateExceptions.
        Logging.log(exception);
    } catch (Exception exception) {
        // Output unexpected Exceptions.
        Logging.log(exception, false);
    }
}

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

---------- DUAL THREADING w/ OWNERSHIP ------
Waiting for thread primary in runner primary.
Waiting for thread secondary in runner secondary.

Библиотека Airbrake-Java обеспечивает мониторинг ошибок в реальном времени и автоматическое создание отчетов об исключениях для всех ваших проектов на основе Java. Тесная интеграция с современной веб-панелью управления Airbrakes гарантирует, что Airbrake-Java предоставляет вам круглосуточные обновления состояния работоспособности вашего приложения и частоты ошибок. Airbrake-Java легко интегрируется со всеми новейшими платформами и платформами Java, такими как Весна , Мавен , log4j , Стойки , Котлин , Граали , Заводной и многое другое. Кроме того, Airbrake-Java позволяет легко настраивать параметры исключений и предоставляет вам полные настраиваемые возможности фильтрации, чтобы вы собирали только наиболее важные ошибки.

Ознакомьтесь со всеми удивительными функциями Airbrake-Java может предложить и сам убедиться, почему так много лучших инженерных команд в мире используют Airbrake для революционизации своих методов обработки исключений!

Оригинал: “https://dev.to/airbrake/java-exception-handling–illegalmonitorstateexception-13a”