Взаимоблокировки JDK-7 SwingWorker?
-
20-09-2019 - |
Вопрос
У меня есть небольшое приложение для обработки изображений, которое делает несколько вещей одновременно с помощью SwingWorker.Однако, если я запущу следующий код (упрощенный отрывок), он просто зависнет в JDK 7 b70 (Windows), но будет работать в 6u16.Он запускает нового работника внутри другого работника и ждет его результата (реальное приложение запускает несколько подчиненных рабочих процессов и ждет всего этого).Использовал ли я здесь какие-то неправильные шаблоны (поскольку в основном в пуле свинг-воркеров есть 3-5 рабочих, у которого, я думаю, есть предел в 10)?
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Swing {
static SwingWorker<String, Void> getWorker2() {
return new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
return "Hello World";
}
};
}
static void runWorker() {
SwingWorker<String, Void> worker
= new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
SwingWorker<String, Void> sw2 = getWorker2();
sw2.execute();
return sw2.get();
}
};
worker.execute();
try {
System.out.println(worker.get());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
runWorker();
}
});
}
}
Решение
Поскольку ссылку еще никто не отключил, похоже, это известная ошибка:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6880336
Удивительно, но менее 100 голосов проголосовали за то, что должно было стать серьезной ошибкой для большинства нетривиальных приложений.
Другие советы
Ваши SwingWorker выполняются в вашем потоке SwingWorker.Поэтому, когда вы видите
Кажется, он зависает на sw2.get(), и в jdk7 есть только один поток с именем swingworker.На jdk6 я вижу сразу 3-5.– кд304
Это связано с тем, что класс SwingWorker — это не поток, а задача, выполняемая в потоке, а конфигурация по умолчанию для ExecutorService для SwingWorker в Java 6 настроена иначе, чем в Java 7.IE ваш SwingWorkerExecutorService (который определен внутри класса SwingWorker) имеет другое значение максимального количества потоков, выделяемых для задач.
//From Java 6 SwingWorker
private static final int MAX_WORKER_THREADS = 10;
public final void execute() {
getWorkersExecutorService().execute(this);
}
private static synchronized ExecutorService getWorkersExecutorService() {
...
private static synchronized ExecutorService getWorkersExecutorService() {
new ThreadPoolExecutor(0, MAX_WORKER_THREADS,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory)
}
У вас есть только один поток, выполняющий задачи SwingWorker, и эта первая задача ожидает завершения второй задачи, которую невозможно запустить, поскольку поток, в котором будет выполняться вторая задача, ожидает завершения второй задачи. прежде чем оно вернется.Создание зависимости потока Swingworker от выполнения другого — верный путь к тупику.Возможно, вы захотите использовать ИсполнительСервис планировать запуск событий в потоке SwingWorker и не ставить одно запланированное событие в зависимость от завершения другого запланированного события.
Глядя на исходный код SwingWorker, кажется, что ExecutorService используется как пул рабочих потоков.Возможно, используемый тип ExecutorService изменился в версиях Java 6 и Java 7.Похоже, ваш код зайдет в тупик, если ExecutorService управляет только одним потоком за раз (как вы, кажется, заметили).
Это связано с тем, что ваш вызов sw2.get() заблокирует текущий поток, который является тем же самым потоком, который попытается использовать sw2.sw2 никогда не сможет выполниться, поскольку первый рабочий процесс блокируется.
Я думаю, что лучшее решение — изменить вашу логику, чтобы вы не вызывали такие цепочки работников Swing.
До обновления JDK 18 вы могли запустить:
public static void main(String[] args) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
System.out.println("ok");
return null;
}
}.execute();
}
Этот код больше не работает просто потому, что SwingWorkers должен выполняться в EDT.
Поэтому вы не можете вкладывать SwingWorkers (sw2 никогда не будет запускаться в вашем примере кода в новых JDK).
Я думаю, замена вложенных вызовов SwingWorkers на вызовы executorService java.util.concurrent.Future является хорошим обходным решением.