Неотвечающая многопоточность с участием Swing и AWT-EventQueue

У меня есть приложение, которое не отвечает и, кажется, находится в тупике или что-то вроде тупика. Смотрите две темы ниже. Обратите внимание, что поток My-Thread@101c блокирует поток AWT-EventQueue-0@301. Однако My-Thread только что звонил java.awt.EventQueue.invokeAndWait(). Итак, AWT-EventQueue-0 блокирует My-Thread (я так думаю).

My-Thread@101c, priority=5, in group 'main', status: 'WAIT'
     blocks AWT-EventQueue-0@301
      at java.lang.Object.wait(Object.java:-1)
      at java.lang.Object.wait(Object.java:485)
      at java.awt.EventQueue.invokeAndWait(Unknown Source:-1)
      at javax.swing.SwingUtilities.invokeAndWait(Unknown Source:-1)
      at com.acme.ui.ViewBuilder.renderOnEDT(ViewBuilder.java:157)
        .
        .
        .
      at com.acme.util.Job.run(Job.java:425)
      at java.lang.Thread.run(Unknown Source:-1)

AWT-EventQueue-0@301, priority=6, in group 'main', status: 'MONITOR'
     waiting for My-Thread@101c
      at com.acme.persistence.TransactionalSystemImpl.executeImpl(TransactionalSystemImpl.java:134)
        .
        .
        .
      at com.acme.ui.components.MyTextAreaComponent$MyDocumentListener.insertUpdate(MyTextAreaComponent.java:916)
      at javax.swing.text.AbstractDocument.fireInsertUpdate(Unknown Source:-1)
      at javax.swing.text.AbstractDocument.handleInsertString(Unknown Source:-1)
      at javax.swing.text.AbstractDocument$DefaultFilterBypass.replace(Unknown Source:-1)
      at javax.swing.text.DocumentFilter.replace(Unknown Source:-1)
      at com.acme.ui.components.FilteredDocument$InputDocumentFilter.replace(FilteredDocument.java:204)
      at javax.swing.text.AbstractDocument.replace(Unknown Source:-1)
      at javax.swing.text.JTextComponent.replaceSelection(Unknown Source:-1)
      at javax.swing.text.DefaultEditorKit$DefaultKeyTypedAction.actionPerformed(Unknown Source:-1)
      at javax.swing.SwingUtilities.notifyAction(Unknown Source:-1)
      at javax.swing.JComponent.processKeyBinding(Unknown Source:-1)
      at javax.swing.JComponent.processKeyBindings(Unknown Source:-1)
      at javax.swing.JComponent.processKeyEvent(Unknown Source:-1)
      at java.awt.Component.processEvent(Unknown Source:-1)
      at java.awt.Container.processEvent(Unknown Source:-1)
      at java.awt.Component.dispatchEventImpl(Unknown Source:-1)
      at java.awt.Container.dispatchEventImpl(Unknown Source:-1)
      at java.awt.Component.dispatchEvent(Unknown Source:-1)
      at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source:-1)
      at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(Unknown Source:-1)
      at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(Unknown Source:-1)
      at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source:-1)
      at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source:-1)
      at java.awt.Component.dispatchEventImpl(Unknown Source:-1)
      at java.awt.Container.dispatchEventImpl(Unknown Source:-1)
      at java.awt.Window.dispatchEventImpl(Unknown Source:-1)
      at java.awt.Component.dispatchEvent(Unknown Source:-1)
      at java.awt.EventQueue.dispatchEvent(Unknown Source:-1)
      at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source:-1)
      at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source:-1)
      at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source:-1)
      at java.awt.EventDispatchThread.pumpEvents(Unknown Source:-1)
      at java.awt.EventDispatchThread.pumpEvents(Unknown Source:-1)
      at java.awt.EventDispatchThread.run(Unknown Source:-1)

Вот метод TransactionalSystemImpl.executeImpl:

private synchronized Object executeImpl(Transaction xact, boolean commit) {
    final Object result;

    try {
        if (commit) { // this is line 134
            clock.latch();
            synchronized(pendingEntries) {
                if (xactLatchCount > 0) {
                    pendingEntries.add(xact);
                } else {
                    xactLog.write(new TransactionEntry(xact, clock.time()));
                }
            }
        }

        final TransactionExecutor executor = transactionExecutorFactory.create(
                xact.getClass().getSimpleName()
        );

        if (executor == null) {
            throw new IllegalStateException("Failed to create transaction executor for transaction: " + xact.getClass().getName());
        }

        result = executor.execute(xact);

    } finally {
        if (commit) clock.unlatch();
    }

    return result;
}

Кто-нибудь знает, что здесь происходит или как это исправить?


person Paul Reiners    schedule 24.05.2010    source источник
comment
Будет сложно понять, что происходит, не видя никакого кода. Можете ли вы опубликовать некоторые?   -  person Seth    schedule 24.05.2010
comment
Я добавил немного кода. Но это настолько сложно, что я не уверен, что стоит задавать вопрос здесь. И придумать более простой пример довольно сложно.   -  person Paul Reiners    schedule 24.05.2010
comment
Почему вы используете два вложенных синхронизированных блока? Это напрашивается на неприятности из моего опыта. Я имею в виду, что весь метод синхронизируется с его экземпляром объекта, в то время как вы также синхронизируете pendingEntries, который кажется переменной-членом этого объекта? Почему бы просто не заблокировать доступ к этой переменной-члену тем же монитором объекта? Это немного упростит ваш сценарий, хотя, вероятно, это не является основной причиной вашей проблемы.   -  person Matthias    schedule 05.12.2013
comment
Спасибо за награду за мой ответ! Я надеюсь, что это поможет вам решить эту проблему.   -  person Stuart Marks    schedule 08.12.2013


Ответы (8)


Похоже, среди моих знакомых разработчиков Swing хорошо известно, что invokeAndWait проблематичен, но, возможно, это не так хорошо известно, как я думал. Кажется, я припоминаю, что видел в документации строгие предупреждения о трудностях правильного использования invokeAndWait, но мне трудно что-либо найти. Я не могу найти ничего в текущей официальной документации. Единственное, что мне удалось найти, это эту строку из старой версии Учебное пособие по Swing 2005 года: (веб-архив)

Если вы используете invokeAndWait, убедитесь, что поток, вызывающий invokeAndWait, не содержит блокировок, которые могут понадобиться другим потокам во время выполнения вызова.

К сожалению, эта строка, похоже, исчезла из текущего руководства по Swing. Даже это, скорее, преуменьшение; Я бы предпочел, чтобы он говорил что-то вроде: «Если вы используете invokeAndWait, поток, который вызывает invokeAndWait, не должен удерживать какие-либо блокировки, которые могут понадобиться другим потокам во время выполнения вызова». В общем, трудно узнать, какие блокировки могут понадобиться другим потокам в любой момент времени, самая безопасная политика, вероятно, состоит в том, чтобы убедиться, что поток, вызывающий invokeAndWait, вообще не удерживает никаких блокировок.

(Это довольно сложно сделать, и именно поэтому я сказал выше, что invokeAndWait проблематичен. Я также знаю, что разработчики JavaFX — по сути замены Swing — определили в javafx.application.Platform класс метод с именем runLater, который функционально эквивалентен invokeLater. Но они намеренно опустил метод, эквивалентный invokeAndWait, потому что его очень сложно правильно использовать.)

Причина довольно проста, чтобы вывести из первых принципов. Рассмотрим систему, аналогичную той, что описана в OP, с двумя потоками: MyThread и потоком отправки событий (EDT). MyThread блокирует объект L и затем вызывает invokeAndWait. Это отправляет событие E1 и ожидает его обработки EDT. Предположим, что обработчику E1 необходимо заблокировать L. Когда EDT обрабатывает событие E1, он пытается заблокировать L. Эта блокировка уже удерживается MyThread, который не освободит ее, пока EDT не обработает E1, но эта обработка заблокирована. от MyThread. Таким образом, мы имеем тупик.

Вот вариант этого сценария. Предположим, мы гарантируем, что обработка E1 не требует блокировки L. Будет ли это безопасно? Нет. Проблема все еще может возникнуть, если непосредственно перед вызовом MyThread invokeAndWait событие E0 отправляется в очередь событий, а обработчик E0 требует блокировки L. Как и прежде, MyThread удерживает блокировку L, поэтому обработка E0 блокируется. E1 находится позади E0 в очереди событий, поэтому обработка E1 также заблокирована. Поскольку MyThread ожидает обработки E1, а он заблокирован E0, который, в свою очередь, заблокирован, ожидая, пока MyThread освободит блокировку L, у нас снова взаимоблокировка.

Это звучит довольно похоже на то, что происходит в приложении OP. Согласно комментариям ОП к этому ответу,

Да, renderOnEDT синхронизируется с чем-то выше в стеке вызовов, с синхронизированным методом com.acme.persistence.TransactionalSystemImpl.executeImpl. И renderOnEDT ждет входа в тот же метод. Итак, это источник тупика, на который похоже. Теперь мне нужно понять, как это исправить.

У нас нет полной картины, но этого, вероятно, достаточно, чтобы продолжить. renderOnEDT вызывается из MyThread, который удерживает блокировку чего-то, пока оно заблокировано в invokeAndWait. Он ожидает обработки события EDT, но мы видим, что EDT заблокирован на чем-то, что хранится в MyThread. Мы не можем точно сказать, что это за объект, но это не имеет значения — EDT явно заблокирован на блокировке, удерживаемой MyThread, и MyThread явно ожидает, пока EDT обработает событие: таким образом, взаимоблокировка .

Также обратите внимание, что мы можем быть уверены, что EDT в настоящее время не обрабатывает событие, отправленное invokeAndWait (аналогично E1 в моем сценарии выше). Если бы это было так, взаимоблокировка происходила бы каждый раз. Кажется, это происходит только иногда и, согласно комментарию ОП к этому ответу, когда пользователь быстро печатает. Поэтому я готов поспорить, что событие, которое в настоящее время обрабатывается EDT, представляет собой нажатие клавиши, которое было отправлено в очередь событий после того, как MyThread взял свою блокировку, но до того, как MyThread вызвал invokeAndWait для отправки E1 в очередь событий, таким образом, это аналогично E0 в моем сценарии выше.

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

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

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

  • Изменение invokeAndWait на invokeLater не вариант. ОП сказал, что это вызывает другие проблемы. Это неудивительно, так как это изменение приводит к тому, что выполнение происходит в другом порядке, поэтому оно дает другие результаты. Я предполагаю, что они были бы неприемлемы.

Если мы не можем удалить блокировки и не можем измениться на invokeLater, нам остается безопасно вызывать invokeAndWait. И «безопасно» означает отказ от блокировок перед вызовом. Это может быть сколь угодно сложно сделать, учитывая организацию приложения OP, но я думаю, что это единственный способ продолжить.

Давайте посмотрим, что делает MyThread. Это очень упрощено, так как в стеке, вероятно, есть куча промежуточных вызовов методов, но в основном это примерно так:

synchronized (someObject) {
    // code block 1
    SwingUtilities.invokeAndWait(handler);
    // code block 2
}

Проблема возникает, когда какое-то событие пробирается в очередь перед обработчиком, и для обработки этого события требуется блокировка someObject. Как мы можем избежать этой проблемы? Вы не можете отказаться от одной из встроенных в Java блокировок монитора в блоке synchronized, поэтому вам нужно закрыть блок, сделать вызов и открыть его снова:

synchronized (someObject) {
    // code block 1
}

SwingUtilities.invokeAndWait(handler);

synchronized (someObject) {
    // code block 2
}

Это может быть сколь угодно сложно, если блокировка someObject берется довольно далеко вверх по стеку вызовов от вызова к invokeAndWait, но я думаю, что такой рефакторинг неизбежен.

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

Вот еще одна проблема. Предположим, что обработчику, запускаемому invokeAndWait, требуется некоторое состояние, загруженное из общего объекта, например,

synchronized (someObject) {
    // code block 1
    SwingUtilities.invokeAndWait(handler(state1, state2));
    // code block 2
}

Вы не могли просто перенести вызов invokeAndWait из синхронизированного блока, так как это потребовало бы несинхронизированного доступа для получения состояния1 и состояния2. Вместо этого вам нужно загрузить это состояние в локальные переменные, находясь внутри блокировки, а затем выполнить вызов, используя эти локальные переменные после снятия блокировки. Что-то типа:

int localState1;
String localState2;
synchronized (someObject) {
    // code block 1
    localState1 = state1;
    localState2 = state2;
}

SwingUtilities.invokeAndWait(handler(localState1, localState2));

synchronized (someObject) {
    // code block 2
}

Техника осуществления вызовов после снятия блокировок называется методом открытого вызова. См. Дуг Ли, Параллельное программирование на Java (2-е издание), раздел 2.4.1.3. Также есть хорошее обсуждение этой техники в Goetz et. и др., Параллелизм в Java на практике, раздел 10.1.4. На самом деле весь раздел 10.1 довольно подробно описывает взаимоблокировку; Я очень рекомендую это.

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

(Наконец, я должен сказать, что, хотя я являюсь сотрудником Oracle, это никоим образом не является официальным заявлением Oracle.)


ОБНОВЛЕНИЕ

Я подумал о еще паре потенциальных рефакторингов, которые могли бы помочь решить проблему. Давайте пересмотрим исходную схему кода:

synchronized (someObject) {
    // code block 1
    SwingUtilities.invokeAndWait(handler);
    // code block 2
}

Это выполняет блок кода 1, обработчик и блок кода 2 по порядку. Если бы мы изменили вызов invokeAndWait на invokeLater, обработчик был бы выполнен после блока кода 2. Легко видеть, что это было бы проблемой для приложения. Вместо этого, как насчет того, чтобы переместить блок кода 2 в invokeAndWait, чтобы он выполнялся в правильном порядке, но все еще в потоке событий?

synchronized (someObject) {
    // code block 1
}

SwingUtilities.invokeAndWait(Runnable {
    synchronized (someObject) {
        handler();
        // code block 2
    }
});

Вот еще один подход. Я не знаю точно, для чего предназначен обработчик, переданный invokeAndWait. Но одна из причин, по которой может потребоваться invokeAndWait, заключается в том, что он считывает некоторую информацию из графического интерфейса, а затем использует ее для обновления общего состояния. Это должно быть в EDT, так как оно взаимодействует с объектами GUI, и invokeLater нельзя использовать, так как это произойдет в неправильном порядке. Это предполагает вызов invokeAndWait перед выполнением другой обработки, чтобы считать информацию из GUI во временную область, а затем использовать эту временную область для продолжения обработки:

TempState tempState;
SwingUtilities.invokeAndWait(Runnable() {
    synchronized (someObject) {
        handler();
        tempState.update();
    }
);

synchronized (someObject) {
    // code block 1
    // instead of invokeAndWait, use tempState from above
    // code block 2
}
person Stuart Marks    schedule 06.12.2013
comment
it's why I said above that invokeAndWait is problematic.- извините, но я вижу, что это немного не соответствует описанию. Это не значит, что это проблематично, оно предназначено для выполнения определенной работы, поэтому просто не используйте его для какой-либо другой работы, и это никоим образом не делает invokeAndWait проблематичным. - person Sage; 06.12.2013
comment
Конечно, в invokeAndWait нет ошибок, и он делает именно то, для чего предназначен. У него также есть предварительное условие, что определенные блокировки не должны удерживаться при его вызове. Ваше заявление просто не использовать его для любой другой работы включает в себя выполнение этого предварительного условия. На практике трудно понять, когда кто-то нарушает это предварительное условие, как показывает проблема ОП. invokeLater не имеет таких ограничений и не рискует зайти в тупик. Таким образом, по сравнению с invokeLater, invokeAndWait проблематичен. - person Stuart Marks; 06.12.2013

Ищу рисунок ответа из достоверных и/или официальных источников.

Event Dispatch Thread and EventQueue

Код обработки событий Swing выполняется в специальном потоке, известном как поток диспетчеризации событий (EDT). Большая часть кода, вызывающего методы Swing, также выполняется в этом потоке. Это необходимо, поскольку большинство методов объекта Swing не являются поточно-ориентированными. Все задачи, связанные с графическим интерфейсом, должны выполняться любые обновления графического интерфейса, а процесс рисования должен выполняться в EDT, что включает в себя перенос запроса в событие и его обработку в EventQueue. Затем события отправляются из одной и той же очереди одно за другим в том порядке, в котором они поставлены в очередь, FIRST IN FIRST OUT. То есть, если Event A поставлен в очередь в EventQueue до Event B, то событие B не будет отправлено до события A.

Класс SwingUtilities имеет две полезные функции, помогающие в задаче рендеринга графического интерфейса:

  • invokeLater(Runnable): вызывает асинхронное выполнение doRun.run() в потоке диспетчеризации событий AWT (EDT). Это произойдет после того, как все ожидающие события AWT будут обработаны, как описано выше.
  • invokeAndWait(Runnable): It has the same function as the invokeLater but it differs from invokeLater for the fact that:
    1. invokeAndWait waits for the task given by it to the EDT, to finish before returning.
    2. он блокирует (ожидает) текущий (т. е. вызывает поток от продолжения его выполнения, отправляя в состояние WAIT посредством синхронизации блокировки.
    3. Он снимет блокировку, как только запрос события, отправленный этой функцией, будет отправлен в EDT, и поток инициатора этой функции сможет продолжить работу.

В исходном коде есть доказательства:

  public static void invokeAndWait(Runnable runnable)
    throws InterruptedException, InvocationTargetException {

     if (EventQueue.isDispatchThread()) 
          throw new Error("Cannot call invokeAndWait from the event dispatcher thread");

     class AWTInvocationLock {}
     Object lock = new AWTInvocationLock();

     InvocationEvent event = new InvocationEvent(Toolkit.getDefaultToolkit(), 
                                                 runnable, lock,
                                                 true);

     synchronized (lock) {  //<<---- locking
           Toolkit.getEventQueue().postEvent(event);
           while (!event.isDispatched()) { //<---- checking if the event is dispatched
                  lock.wait(); //<---- if not tell the current invoking thread to wait 
              }
            }

      Throwable eventThrowable = event.getThrowable();
      if (eventThrowable != null) {
          throw new InvocationTargetException(eventThrowable);
      }
  }

Это объясняет проблему:

My-Thread только что вызвал java.awt.EventQueue.invokeAndWait(). Итак, AWT-EventQueue-0 блокирует My-Thread (я полагаю).

Чтобы объяснить сценарий взаимоблокировки, который у вас, вероятно, возникает, давайте рассмотрим пример:

class ExampleClass 
{

    public synchronized void renderInEDT(final Thread t)
    {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {

                    @Override
                    public void run() {
                        System.out.println("Executinf invokeWait's Runnable ");
                        System.out.println("invokeWait invoking Thread's state: "+t.getState());

                        doOtherJob();
                    }
                });
            } catch (InterruptedException ex) {
                Logger.getLogger(SwingUtilitiesTest.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvocationTargetException ex) {
                Logger.getLogger(SwingUtilitiesTest.class.getName()).log(Level.SEVERE, null, ex);
            }

    }

    public synchronized void renderInEDT2(final Thread t)
    {
                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        System.out.println("Executing invokeLater's Runnable ");
                        System.out.println("invokeLater's invoking Thread's state: "+t.getState());
                        doOtherJob();
                    }
                });
        try {
            Thread.sleep(3000);
        } catch (InterruptedException ex) {
            Logger.getLogger(ExampleClass.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


    public synchronized void doOtherJob()
    {
        System.out.println("Executing a job inside EDT");
    }
}

Как видите, я объявил три синхронизированных функции:

  1. renderInEDT(final Thread t): выполнить задачу Runnable в EDT bySwingUtilities.invokeAndWait
  2. renderInEDT2(final Thread t): выполнить задачу Runnable в EDT до SwingUtilities.invokeLater
  3. doOtherJob() : Эта функция вызывается каждым из методов Runnable'S run() двух вышеуказанных функций.

Ссылка вызывающего потока передается для проверки состояния каждого вызова функции SwingUtilities. Теперь, если мы вызовем renderInEDT() для экземпляра exmpleClass из ExampleClass: Здесь Thread t объявлено в контексте класса:

    t =  new Thread("TestThread"){
                 @Override
                 public void run() {
                    exmpleClass.renderInEDT(t);
                 }

    };
    t.start();

Вывод будет:

Executing invokeWait's Runnable 
invokeWait invoking Thread's state: WAITING

Метод doOtherJob() никогда не выполняется в EDT, опубликованном SwingUtilities.invokeAndWait, потому что возникает тупиковая ситуация. Поскольку renderInEDT() синхронизируется и выполняется внутри потока, а именно t, EDT должен будет ждать выполнения doOtherJob() до тех пор, пока первый вызывающий поток не завершит выполнение метода renderInEDT(final Thread t), как описано в official tutorial source of synchronized method:

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

Следовательно, EDT ожидает завершения (и приостановки) выполнения потока t, но поток t фактически заблокирован и переведен в состояние ожидания методом SwingUtilities.invokeAndWait, как описано выше, поэтому он не может завершить свое выполнение: Bth EDT и поток t ждут друг друга, чтобы завершить их выполнение.

Давайте посмотрим на приведенный выше пример с case: если мы отправим задачу события, используя SwingUtilities.invokeLater, как это будет очевидно, если мы выполним функцию renderInEDT2() на экземпляре exampleClass из потока:

  t =  new Thread("TestThread"){
                 @Override
                 public void run() {
                    exmpleClass.renderInEDT2(t);
                 }   
    };
  t.start();

На этот раз вы увидите, что вызов функции продолжает нормально производить следующий вывод:

Executing invokeLater's Runnable 
invokeLater's invoking Thread's state: TIMED_WAITING
Executing a job inside EDT

На этот раз doOtherJob() выполняется EDT, как только первый вызывающий поток renderInEDT2() завершает его выполнение: чтобы подчеркнуть, я перевел поток в спящий режим (3 с), чтобы проверить время выполнения, и, следовательно, он показывает состояние TIMED_WAITING.

Вот что объясняет вашу вторую проблему: как говорит исключение, а также упомянутое вами в одном из ваших комментариев:

enderOnEDT синхронизируется с чем-то выше в стеке вызовов, с синхронизированным методом com.acme.persistence.TransactionalSystemImpl.executeImpl. И renderOnEDT ждет входа в тот же метод. Итак, это источник тупика, на который похоже. Теперь мне нужно понять, как это исправить.

Однако

  • SwingUtilities.invokeAndWait(Runnable) особенно используется, когда мы хотим заблокировать или ожидать поток и запросить у пользователя подтверждение, следует ли нам продолжать использовать JOptionPane/JDialogue/JFileChooser и т. д.
  • В противном случае, чтобы опубликовать задачу рендеринга графического интерфейса в EventQueue, используйте SwingUtilities.invokeLater(Runnable).
  • Хотя я ждал EDT, используя Thread.sleep(time) для демонстрационных целей, пожалуйста, не делайте ничего такого, что может заблокировать EDT на указанное время, даже если оно небольшое, иначе ваш Swing получит заморожены и заставят вас убить его.
  • Мы не должны выполнять какую-либо вычислительную работу или операции чтения/записи или любые другие вещи, которые не связаны с задачей рендеринга графического интерфейса внутри EDT, также называемой внутри Runnable функций invokeAndWait и invokeLater.

На этом этапе вы сами должны быть в состоянии выяснить и решить свою проблему, потому что вы не предоставляете нам достаточно подробностей своего кода. Я думаю, что при публикации задачи рендеринга графического интерфейса в очереди событий с использованием синхронизированной функции, такой как enderOnEDT, как вы сказали в своем комментарии, я не вижу причин для вызова другой синхронизированной функции из ее Runnable. Скорее поместите свою функцию рендеринга в этот Runnable напрямую. Это моя единственная цель объяснить механизм очереди событий и EDT.

Ссылка:

  1. The Event Dispatch Thread
  2. Class EventQueue
  3. Initial Threads
  4. SwingUtilities.invokeAndWait(Runnable doRun) documentation
person Sage    schedule 03.12.2013

Трудно сказать, не видя кода, но из трассировки стека похоже, что вы запускаете какой-то транзакционный код из потока диспетчеризации событий. Запускает ли этот код экземпляр My-Thread? EDT может быть заблокирован в ожидании My-Thread из транзакционного кода, но My-Thread не может завершиться, потому что ему нужен EDT.

Если это так, вы можете использовать SwingUtilities.invokeLater для рендеринга, чтобы EDT завершил транзакционный код, а затем отобразил обновления. Или вы не можете выполнить транзакционный код из EDT. Для реальной работы, не связанной с рендерингом, вам следует использовать SwingWorker, чтобы избежать тяжелой обработки в EDT.

person Jeff Storey    schedule 24.05.2010
comment
Нет, транзакционный код не запускает экземпляр My-Thread. Это уже работает в то время. - person Paul Reiners; 25.05.2010
comment
Ok. Другое дело проверить. Вы уверены, что вызов ViewBuilder.renderOnEDT также не происходит из EDT (поскольку вы, вероятно, не захотите вызывать invokeAndWait) из EDT. Или возможно, что вызов renderOnEDT синхронизирован с чем-то? Затем, если EDT попытается синхронизироваться с тем же объектом (во второй опубликованной вами трассировке стека), пока My-Thread находится в invokeAndWait, у вас возникнет взаимоблокировка. - person Jeff Storey; 25.05.2010
comment
ViewBuilder.renderOnEDT иногда вызывается в EDT, но в этом случае мы не используем invokeAndWait: if (SwingUtilities.isEventDispatchThread()) { container = renderable.render(); } else { try { SwingUtilities.invokeAndWait(renderable); } поймать (исключение e) { бросить новое исключение RuntimeException (e); } контейнер = renderable.getContainer(); } - person Paul Reiners; 25.05.2010
comment
Да, renderOnEDT синхронизируется с чем-то выше в стеке вызовов, с синхронизированным методом com.acme.persistence.TransactionalSystemImpl.executeImpl. И renderOnEDT ждет входа в тот же метод. Итак, это источник тупика, на который похоже. Теперь мне нужно понять, как это исправить. - person Paul Reiners; 25.05.2010
comment
Опять же, не зная всего кода, я не могу предоставить больше подробностей. Но в целом следует избегать синхронизации и длительных операций в EDT. Таким образом, использование invokeLater должно помочь запустить операцию в EDT, не блокируя ее. Кроме того, как я уже упоминал, вам следует изучить возможность использования SwingWorker, а не выполнения транзакций в EDT. - person Jeff Storey; 25.05.2010

Я подозреваю, что строка 134, которую вы цитируете, не является настоящей строкой 134 (может быть вызвана устаревшим кодом или некоторыми другими проблемами). Кажется, что 134 ждет монитор, что, скорее всего, означает synchronized(pendingEntries) (или clock.latch(), что, я думаю, является какой-то защелкой обратного отсчета?)

Судя по трассировке стека, поток диспетчеризации событий AWT ожидает монитор, удерживаемый MyThread.

Пожалуйста, проверьте базу кода на трассировке стека MyThread. Я полагаю, что где-то он синхронизируется на pendingEntries, затем он использовал invokeAndWait, чтобы попросить поток диспетчеризации событий что-то сделать, и, в свою очередь, поток диспетчеризации событий ожидает pendingEntries, что вызвало взаимоблокировку.


Предложение, которое немного не по теме: кажется, что ваш поток диспетчеризации событий делает намного больше, чем должен. Я не думаю, что выполнение этой обработки транзакций и т. Д. В потоке диспетчеризации событий является хорошим выбором. Такое действие может быть медленным (и в этом случае даже блокировать поток диспетчеризации событий), что приведет к тому, что пользовательский интерфейс не будет отвечать на запросы.

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

person Adrian Shum    schedule 03.12.2013

Некоторый поток (я предполагаю, что My-Thread@101c) является synchronized в вашем экземпляре TransactionalSystemImpl. Поток пользовательского интерфейса пытается войти в executeImpl, но заблокирован на мониторе synchronized и не может. Где еще используется экземпляр TransactionalSystemImpl (с записью synchronized)? Вероятно, между

  at com.acme.ui.ViewBuilder.renderOnEDT(ViewBuilder.java:157)
    .
    .
    .
  at com.acme.util.Job.run(Job.java:425)
person John Vint    schedule 24.05.2010
comment
Я попробовал invokeLater, но это вызвало другие проблемы. Возможно, этот код слишком сложен, чтобы задавать здесь этот вопрос. Однако придумать небольшой тестовый пример, воспроизводящий проблему, было бы очень сложно. - person Paul Reiners; 25.05.2010
comment
Да, это то, что происходит. Пользователь быстро набирает символы. Итак, как это исправить? - person Paul Reiners; 25.05.2010
comment
Возможно, мне придется изменить его на invokeLater(), а затем исправить возникшие проблемы. - person Paul Reiners; 25.05.2010
comment
+1 за блестящий и лаконичный анализ проблемы - person flup; 06.12.2013

Если нет других взаимоблокировок, вы можете преобразовать вызов EventQueue.invokeLater(Runnable) в блокирующую версию, которая ожидает завершения вашего Runnable:

if (EventQueue.isDispatchThread()) r.run();
else {
    final Lock lock = new ReentrantLock();
    final AtomicBoolean locked = new AtomicBoolean(true);
    final Condition condition = lock.newCondition();

    EventQueue.invokeLater(() -> {
        r.run();
        try {
            lock.lock();
            locked.set(false);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    });

    try {
        lock.lock();
        while (locked.get())
            condition.await();
    } finally {
        lock.unlock();
    }
}
person benez    schedule 19.03.2017

invokeAndWait не разрешен из EDT и должен выполняться через исключение. Но, глядя на трассировку стека, похоже, что вы используете поток-оболочку, что позволяет вам вызывать invokeAndWait, но это неправильно. Изменение его на SwingUtilities.invokeLater должно решить эту проблему.

Альтернативное решение: если оно того стоит, вы также можете изучить класс SwingWroker для рабочих потоков. Ссылка здесь:

http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

Просто добавьте дополнительную информацию о тупиковой ситуации: в Javadoc invokeAndWait четко упоминается: «Это произойдет после обработки всех ожидающих событий». Это включает в себя текущее событие, вызывающее invokeAndWait. invokeAndWait будет ожидать завершения текущего события, а текущее событие будет ожидать завершения вызова invokeAndWait. Это гарантированный тупик, и поэтому он не разрешен.

person Mak    schedule 03.12.2013
comment
invokeAndWait не вызывается из EDT. Звонят из My-Thread. - person John Vint; 03.12.2013
comment
Если invokeAndWait вызывается из потока-оболочки, то он больше не вызывается в EDT. - person Radiodef; 04.12.2013

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

Строка 134 не содержит ничего, что могло бы вызвать блокировку, поэтому информация в трассировке стека должна быть отключена. Я предполагаю, что фиксация истинна, так как в противном случае код зависал бы при создании исполнителя, который достаточно сложен для JVM, чтобы не оптимизировать его из трассировки стека. Поэтому строка, на которой он зависает, должна быть вызовом clock.latch(). Я не знаю, что он делает, но, учитывая структуру try/finally, это должно быть что-то важное, возможно, связанное с многопоточностью.

Потом "почему зависает". Как вы уже сказали, два потока пытаются получить доступ к потоку Swing для какой-то работы, но по крайней мере один никогда не возвращается, что, очевидно, приводит к взаимоблокировке всех компонентов Swing. Чтобы заблокировать поток Swing, кто-то должен хотя бы его вызвать, но ни одна строка в представленном коде этого не делает, так что еще раз: сложное угадывание.

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

Остаются два кандидата для этой проблемы: один — clock.latch(), который может вызвать проблему, но только если он внутри выполняет какую-либо форму синхронизации, например, будучи объявленным как synchronized void latch(), хотя я не могу сказать, как это будет блокироваться, так как есть слишком мало информации. Но, основываясь на представленном коде, я предполагаю, что остальная часть программы находится в таком же плохом состоянии, так что это не так уж и далеко. Вторая возможность — это synchronized(pendingEntries), но опять же: в представленных данных нет доказательств, которые могли бы вызвать это, но, учитывая пример, все идет своим чередом.

person TwoThe    schedule 05.12.2013
comment
Проблема не во внутреннем коде функции: synchronized Object executeImpl() проблема в том, что эта функция synchronized, которую я объяснил. Спасибо - person Sage; 05.12.2013
comment
Я вовсе не не согласен с вашим утверждением, я просто пытаюсь угадать, откуда исходит этот синхронизированный вызов. - person TwoThe; 05.12.2013