Как FileDialog блокирует поток, не блокируя поток?

Я использую java.awt.FileDialog. Это модальный диалог, поэтому, когда я вызываю setVisible(true), он блокируется, пока я не выберу файл или иным образом не закрою окно.

Однако в документации для setVisible есть любопытная строка:

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

Мне было интересно, как это работает, поэтому я решил проверить это, запланировав некоторые другие события, пока диалог виден. Я использовал ScheduledExecutorService, чтобы запускать событие раз в секунду.

Согласно моему эксперименту, один и тот же поток может продолжать выполнение кода, даже если он должен быть заблокирован в ожидании завершения setVisible. Вот полный код, который я использовал для его тестирования.

public class Example {
  static Thread edt;
  static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

  static void checkThread(String source) {
    Thread thisThread = Thread.currentThread();
    String threadCheck = thisThread == edt ? "same thread" : "different thread";
    System.out.println(source + " is running on " + thisThread + " (" + threadCheck + ")");
  }

  public static void main(String[] args) throws Exception {
    EventQueue.invokeAndWait(() -> edt = Thread.currentThread());

    executor.scheduleAtFixedRate(() -> EventQueue.invokeLater(() -> checkThread("Timer")), 1, 1, TimeUnit.SECONDS);

    EventQueue.invokeLater(() -> {
      checkThread("File picker");
      FileDialog dialog = new FileDialog((Frame) null);
      dialog.setVisible(true);
      System.out.println("You picked " + dialog.getFile());
      checkThread("File picker");
      executor.shutdownNow();
    });
  }
}
  1. Сначала я беру ссылку на поток отправки событий.
  2. Затем я запускаю планировщик, который каждую секунду будет запускать событие в EDT. Это событие вызовет мою функцию checkThread, которая распечатает имя потока и сравнит его со ссылкой, которую я получил ранее.
  3. Наконец, я отправляю еще одно событие, которое покажет диалоговое окно моего файла. До и после отображения диалога я снова вызываю свою функцию checkThread.

Мой вывод выглядит примерно так:

File picker is running on Thread[AWT-EventQueue-0,6,main] (same thread)
Timer is running on Thread[AWT-EventQueue-0,6,main] (same thread)
Timer is running on Thread[AWT-EventQueue-0,6,main] (same thread)
Timer is running on Thread[AWT-EventQueue-0,6,main] (same thread)
You picked somefile.txt
File picker is running on Thread[AWT-EventQueue-0,6,main] (same thread)

Из этого вывода я вижу, что все мои события выполнялись в одном и том же «потоке». Хотя setVisible был заблокирован, три других мероприятия, по-видимому, все еще могли проводиться. Я даже проверил ссылочное равенство на случай, если toString() мне врал. Поэтому я теперь пишу «тема» в кавычках, потому что, основываясь на этом результате, я вообще не могу поверить, что это действительно тема.

Как этот «поток» может продолжать выполнение кода, пока он заблокирован? Ни один другой поток, который я когда-либо видел, на Java или любом другом языке, не может сделать это. Конечно, мы могли бы добиться чего-то подобного с виртуальными потоками, но даже в этом случае каждый отдельный виртуальный поток по-прежнему действует по правилам и прекращает работу, когда его блокируют.

Копая, я наткнулся на класс EventDispatchThread, расширяющий Thread. Правильно ли было бы предположить, что EventDispatchThread — это вообще не настоящая тема?

Моё предположение таково:

  • EventDispatchThread должен поддерживаться более чем одним реальным потоком, поскольку он может продолжать выполнять события, пока одно событие заблокировано.
  • Необходимо различать разные типы блокировки, поскольку простой вызов Thread.sleep() предотвращает отправку других событий.
  • Либо он рискует запустить некоторые события параллельно, либо выполняет какую-то синхронизацию, чтобы гарантировать, что только один из этих специальных заблокированных потоков может возобновиться одновременно.
  • И в любом случае, поскольку события, очевидно, могут чередоваться, должны быть некоторые явно опасные и нелогичные последствия, когда дело доходит до совместного использования изменяемого состояния между событиями.

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

Буду признателен за любые ссылки или документацию, подтверждающие или опровергающие это предположение. Бонусные баллы за то, что вы прольете свет на причину этого.

Потоки — довольно фундаментальная часть языка, и я рассчитываю на их предсказуемое поведение. Как эта тема может быть одновременно заблокирована и не заблокирована? Или, если это не поток, то что он делает, притворяясь им?

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

PaulProgrammer 15.03.2024 21:10

См. EventQueue.createSecondaryLoop(). Диалоги AWT могут использовать или не использовать этот класс напрямую; если нет, то они, вероятно, используют что-то эквивалентное.

VGR 16.03.2024 00:08
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ни одно из ваших предположений не верно. Если вы вызываете «пожалуйста, откройте мне диалоговое окно файла» из EDT, затем запускается код, который рисует диалоговое окно файла (и в середине этого процесса ваше приложение не отвечает. Это не имеет значения; его можно быстро нарисовать). Затем, когда приходит время дождаться взаимодействия пользователя с ним, он просто делает что-то вроде:

while (true) {
  UiEvent event = UiQueue.fetchNext();
  if (event.source == myself) break;
  event.handle();
}
// handle the event

где EDT обычно выполняет этот цикл while без части if. Я, конечно, несколько упростил ситуацию.

Обычно этот цикл обработки EDT возникает, когда ваш код, размещенный в EDT, возвращается. Однако диалоговое окно открытия файла также может «хостировать» очередь обработки EDT, закрываясь при возникновении события, что означает, что действие диалогового окна открытия файла готово завершиться тем или иным способом (например, событие в очереди EDT является либо «Отмена» или кнопку «Открыть» в диалоговом окне этого файла).

Конечно! Я слишком долго ломал голову над этим и не могу поверить, что не подумал об этом объяснении. Это имеет смысл.

Sam 15.03.2024 19:50

Другие вопросы по теме