Беспокойная резьба с участием Swing и AWT-EventQueue
У меня есть приложение, которое не отвечает и, кажется, находится в тупике или что-то вроде тупика. См. Два потока ниже. Обратите внимание, что поток [email protected]
блокирует [email protected]
. Однако My-Thread
только что назвал java.awt.EventQueue.invokeAndWait()
. Поэтому AWT-EventQueue-0
блокирует My-Thread
(я считаю).
[email protected], priority=5, in group 'main', status: 'WAIT'
blocks [email protected]
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)
[email protected], priority=6, in group 'main', status: 'MONITOR'
waiting for [email protected]
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;
}
Кто-нибудь знает, что здесь происходит или как это исправить?
Ответы
Ответ 1
Как известно, среди разработчиков Swing моего знакомого известно, что invokeAndWait
проблематично, но, возможно, это не так хорошо известно, как я думал. Кажется, я помню, что видел серьезные предупреждения в документации о трудностях с использованием invokeAndWait
, но мне трудно найти что-то. Я не могу найти что-либо в текущей официальной документации. Единственное, что мне удалось найти, это эта строка из старой версии Swing Tutorial с 2005 г.: (веб-архив)
Если вы используете invokeAndWait
, убедитесь, что поток, вызывающий invokeAndWait, не содержит блокировок, которые понадобятся другим потокам во время вызова.
К сожалению, эта строка, похоже, исчезла из текущего учебника Swing. Даже это скорее преуменьшение; Я бы предпочел, чтобы он сказал что-то вроде: "Если вы используете invokeAndWait
, поток, который вызывает invokeAndWait
, не должен удерживать блокировки, которые нужны другим потокам во время вызова." В общем, трудно понять, какие блокировки для других потоков могут потребоваться в любой момент времени, наиболее безопасная политика, вероятно, заключается в том, что поток, вызывающий invokeAndWait
, не содержит никаких блокировок вообще.
(Это довольно сложно сделать, и поэтому я сказал выше, что invokeAndWait
проблематично. Я также знаю, что разработчики JavaFX - по существу замена Swing - определены в javafx.application.Platform классу метод под названием runLater
, который функционально эквивалентен invokeLater
. Но они преднамеренно пропустили эквивалентный метод invokeAndWait
, потому что это очень сложно правильно использовать.)
Причина довольно прямолинейна, исходя из первых принципов. Рассмотрим систему, аналогичную той, которая описана OP, с двумя потоками: MyThread и Thread Dispatch Thread (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. Согласно комментариям OP на этот ответ,
Да, renderOnEDT синхронизируется каким-то образом в стеке вызовов, метод com.acme.persistence.TransactionalSystemImpl.executeImpl, который синхронизирован. И renderOnEDT ждет ввода того же метода. Таким образом, это источник тупика. Теперь я должен выяснить, как это исправить.
У нас нет полной картины, но этого, вероятно, достаточно, чтобы продолжить. renderOnEDT
вызывается из MyThread, который блокирует что-то, пока он заблокирован в invokeAndWait
. Он ожидает, что событие будет обработано EDT, но мы видим, что EDT заблокирован чем-то, что хранится в MyThread. Мы не можем точно сказать, какой именно объект, но это не имеет значения. EDT явно заблокирован блокировкой MyThread, и MyThread явно ждет, пока EDT обработает событие: таким образом, тупик.
Заметим также, что мы можем быть достаточно уверены, что EDT в настоящее время не обрабатывает событие, отправленное invokeAndWait
(аналогично E1 в моем сценарии выше). Если это так, тупик будет возникать каждый раз. Кажется, что это происходит только иногда, и в соответствии с комментарием OP на этом ответе, когда пользователь быстро набирает текст. Поэтому я бы поставил на то, что событие, которое в настоящее время обрабатывается EDT, представляет собой нажатие клавиши, которое было отправлено в очередь событий после того, как MyThread взял свою блокировку, но перед тем, как MyThread вызвал invokeAndWait
, чтобы отправить E1 в очередь событий, таким образом, он аналогичен к E0 в моем сценарии выше.
До сих пор это, вероятно, в основном резюме проблемы, собранной из других ответов и комментариев OP по этим ответам. Прежде чем переходить к обсуждению решения, вот некоторые предположения, которые я делаю о приложении 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
}
Техника совершения вызовов после освобождения шлюзов называется методом открытого вызова. См. Doug Lea, Параллельное программирование на Java (2-е издание), с. 2.4.1.3. Существует также хорошее обсуждение этой техники в Goetz et. al., Java Concurrency На практике, п. 10.1.4. Фактически, весь раздел 10.1 достаточно тщательно охватывает тупик; Я рекомендую его очень.
В целом, я считаю, что использование методов, описанных выше, или в цитированных книгах, позволит решить эту проблему взаимоблокировки правильно и безопасно. Однако я уверен, что для этого потребуется много тщательного анализа и сложной реструктуризации. Однако я не вижу альтернативы.
(Наконец, я должен сказать, что, хотя я сотрудник Oracle, это никоим образом не является официальным выражением Oracle.)
UPDATE
Я подумал о еще нескольких потенциальных рефакторингах, которые могли бы помочь решить проблему. Давайте пересмотреть исходную схему кода:
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
перед выполнением другой обработки, чтобы считывать информацию из графического интерфейса во временную область, а затем использовать эту временную область для продолжения обработки:
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
}
Ответ 2
Ищите ответный рисунок из достоверных и/или официальных источников.
Event Dispatch Thread and EventQueue
Код обработки событий Swing запускается в специальном потоке, известном как Thread Dispatch Thread (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)
: он имеет ту же функцию, что и invokeLater
, но отличается от invokeLater
тем, что:
-
invokeAndWait
ждет задание, данное им EDT, для завершения перед возвратом.
- он блокирует (ожидает) текущий (то есть он вызывает поток от продолжения его выполнения, отправив в состояние
WAIT
посредством синхронизации блокировки.
- Он освободит блокировку, как только запрос события, отправленный этой функцией, будет отправлен в EDT, и поток invoker этой функции может продолжить.
Исходный код имеет доказательства:
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 Runnable ");
System.out.println("invokeWait invoking Thread 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 Runnable ");
System.out.println("invokeLater invoking Thread 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");
}
}
Как вы можете видеть, я объявил три синхронизированные функции:
-
renderInEDT(final Thread t)
: выполнить задачу Runnable в EDT SwingUtilities.invokeAndWait
-
renderInEDT2(final Thread t)
: выполнить задачу Runnable в EDT на SwingUtilities.invokeLater
-
doOtherJob()
: эта функция вызывается каждой из двух функций Runnable
run()
.
Ссылка на вызывающий поток передается для проверки состояния для каждого вызова функции SwingUtilities
. Теперь, если мы вызываем renderInEDT()
в экземпляре exmpleClass
ExampleClass
: здесь Thread t
объявляется в контексте класса:
t = new Thread("TestThread"){
@Override
public void run() {
exmpleClass.renderInEDT(t);
}
};
t.start();
Выход будет:
Executing invokeWait Runnable
invokeWait invoking Thread 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
ждут друг друга, чтобы выполнить их выполнение.
Посмотрим вышеприведенный пример: если мы отправим событие с использованием SwingUtilities.invokeLater
, поскольку это будет очевидно, если мы выполним функцию renderInEDT2()
в экземпляре ExampleClass
из потока:
t = new Thread("TestThread"){
@Override
public void run() {
exmpleClass.renderInEDT2(t);
}
};
t.start();
На этот раз вы увидите, что вызов функции продолжает нормально производить следующий вывод:
Executing invokeLater Runnable
invokeLater invoking Thread state: TIMED_WAITING
Executing a job inside EDT
На этот раз doOtherJob()
будет выполняться EDT, как только будет выполняться первый вызывающий поток renderInEDT2()
: чтобы подчеркнуть, я поставил поток в сон (3s), чтобы проверить время выполнения, и, следовательно, он показывает состояние TIMED_WAITING
.
Вот что объясняет ваш второй вопрос: поскольку исключение говорит и также упоминается вами в одном из ваших комментариев:
enderOnEDT синхронизируется каким-то образом в стеке вызовов, метод com.acme.persistence.TransactionalSystemImpl.executeImpl, который синхронизируется. И renderOnEDT ждет ввода того же метода. Таким образом, это источник тупика. Теперь я должен выяснить, как это исправить.
Однако,
-
SwingUtilities.invokeAndWait(Runnable)
особенно используется, когда мы хотим заблокировать или ждать поток и запросить подтверждение пользователя, если мы продолжим использовать JOptionPane/JDialogue/JFileChooser
и т.д.
- В противном случае, чтобы опубликовать задачу рендеринга GUI в
EventQueue
, используйте SwingUtilities.invokeLater(Runnable)
.
- Хотя, я ожидал EDT, используя
Thread.sleep(time)
для демонстрационных целей. Пожалуйста, не делайте ничего такого, что может блокировать EDT для заметного количества времени, даже если его немного, иначе ваш Swing замерзнет и заставит вас убить его.
- Мы не должны выполнять какую-либо вычислительную работу или операцию чтения/записи или любую другую вещь, не связанную с задачей рендеринга GUI внутри EDT, а также внутри
Runnable
функции invokeAndWait
и invokeLater
.
На этом этапе вы сами должны уметь выяснить и решить свою проблему, потому что вы не предоставляете нам подробные сведения о своем коде. Я думаю, что при отправке вашей задачи рендеринга графического интерфейса в очередь событий с использованием синхронизированной функции, например enderOnEDT
, как вы сказали в своем комментарии, я не вижу причин для вызова из нее другой синхронизированной функции Runnable
. Скорее положите свою функцию рендеринга в этот Runnable
. Это моя единственная цель - объяснить механизм очереди событий и EDT.
Справка:
Ответ 3
Трудно сказать, не видя кода, но из трассировки стека, похоже, вы запускаете какой-то транзакционный код из потока отправки событий. Затем этот код запускает экземпляр My-Thread? EDT может быть заблокирован в ожидании My-Thread из транзакционного кода, но My-Thread не может закончить, потому что ему нужен EDT.
Если это так, вы можете использовать SwingUtilities.invokeLater
для вашего рендеринга, чтобы EDT завершил транзакционный код, а затем отобразит обновления. Или вы не можете выполнить транзакционный код из EDT. Для фактической работы, не связанной с рендерингом, вы должны использовать SwingWorker, чтобы избежать большой обработки на EDT.
Ответ 4
Некоторые потоки (я предполагаю [email protected]
) synchronized
в вашем экземпляре TransactionalSystemImpl. Поток пользовательского интерфейса пытается войти в executeImpl
, но заблокирован на мониторе synchronized
и не может. Где еще используется экземпляр TransactionalSystemImpl (с записью synchronized
)? Вероятно, между
at com.acme.ui.ViewBuilder.renderOnEDT(ViewBuilder.java:157)
.
.
.
at com.acme.util.Job.run(Job.java:425)
Ответ 5
Я подозреваю, что строка 134, которую вы цитировали, не является реальной линией 134 (может быть вызвана устаревшим кодом или некоторыми другими проблемами). Кажется, что 134 ждет монитор, который, скорее всего, означает synchronized(pendingEntries)
(или clock.latch()
, который, я думаю, является своего рода защелкой отсчета?)
Из трассировки стека поток диспетчеризации событий AWT ждет монитора, который хранится в MyThread.
Пожалуйста, проверьте базу кода на трассировке стека MyThread. Я верю, что где-то он синхронизируется на pendingEntries
, затем он использовал invokeAndWait
, чтобы попросить поток диспетчеризации событий что-то сделать, и, в свою очередь, поток диспетчеризации событий ожидает pendingEntries
, что вызвало тупик.
Предложение, которое немного отличается от тематики: ваше сообщение о передаче событий кажется намного большим, чем нужно. Я не думаю, что это обработка транзакций и т.д. В случае, когда диспетчерский поток является хорошим выбором. Такое действие может быть медленным (и в этом случае даже блокирует поток диспетчеризации событий), что приведет к тому, что пользовательский интерфейс будет невосприимчивым.
Разделение такого действия на отдельный поток/исполнитель кажется лучшим выбором для меня.
Ответ 6
Если никаких других взаимоблокировок нет, вы можете преобразовать вызов в 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();
}
}
Ответ 7
invokeAndWait не допускается из EDT и должен через исключение. Но, глядя на stacktrace, это выглядит так, потому что вы используете свой поток оболочки, что позволяет вам вызвать invokeAndWait, но это неправильно. Изменение этого параметра на SwingUtilities.invokeLater должно устранить эту проблему.
Альтернативное решение: если это стоит, вы также можете посмотреть класс SwingWroker для рабочих потоков. Вот ссылка:
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
Просто добавив дополнительную информацию о тупике:
В Javadoc invokeAndWait четко указано: "Это произойдет после обработки всех ожидающих событий". Это включает в себя текущее событие вызывает invokeAndWait. invokeAndWait будет ждать завершения текущего события, и текущее событие ждет завершения invokeAndWait. Это гарантированный тупик, и почему он не разрешен.
Ответ 8
Трудно сказать с небольшой информацией, которую вы нам даете. Затем код заполнен плохой практикой кодирования, поскольку в основном каждая вторая строка может вызвать некоторые проблемы. Поэтому все, что я могу сделать, это некоторые сложные догадки:
Строка 134 не содержит ничего, что могло бы вызвать блокировку, поэтому информация в стеке должна быть отключена. Я предполагаю, что фиксация истинна, поскольку в противном случае код зависает при создании исполнителя, что достаточно сложно для JVM, чтобы не оптимизировать это из stacktrace. Поэтому линия, в которой он висит, должна быть вызовом clock.latch()
. Я не знаю, что он делает, но с учетом структуры try/finally он должен быть чем-то важным, возможно, связанным с потоковой обработкой.
Затем "зачем он висит". Как вы уже сказали, две темы пытаются получить доступ к потоку Swing для некоторой работы, но по крайней мере один никогда не возвращается, что, очевидно, приводит к взаимоблокировке всех компонентов Swing. Чтобы заблокировать поток Swing, кто-то должен хотя бы называть его, но ни одна строка в представленном коде не делает этого, поэтому снова: сложные угадывания.
Первый синхронизированный оператор не может быть причиной, поскольку он уже передан, второй не находится в stacktrace, но, учитывая, что этот может быть неисправен, возможно, это будет просто вызвано благодарностью благодаря JVM-коду переупорядочение для оптимизации.
Это оставляет двух кандидатов для этой проблемы: один - это clock.latch()
, что может вызвать проблему, но только если она внутренне выполняет любую форму синхронизации, например объявляется как synchronized void latch()
, хотя я не могу сказать, как это блокировалось бы, поскольку информации было слишком мало. Но, основываясь на представленном коде, я предполагаю, что остальная часть программы находится в равной плохом состоянии, так что это далеко не так. Вторая возможность - это synchronized(pendingEntries)
, но опять же: в представленных данных нет доказательств, которые могли бы вызвать это, но, учитывая пример, все идет.