Я использую это упражнение в качестве педагогического инструмента, чтобы помочь мне усвоить некоторые концепции программирования Java GUI. Я ищу общее понимание, а не подробное решение одной конкретной проблемы. Я ожидаю, что такое «правильное» кодирование многому научит меня, как подходить к будущим многопоточным проблемам. Если это слишком общее для этого форума, возможно, это относится к Программистам?
Я моделирую кардридер. У него есть графический интерфейс, позволяющий загружать карты в загрузочную воронку и нажимать «Старт» и т. Д., Но его основным «клиентом» является ЦП, работающий в отдельном потоке и запрашивающий карты.
Устройство чтения карт поддерживает единственный буфер. Если поступает запрос карты и буфер пуст, устройство чтения карт должно считать карту из бункера (что занимает 1/4 секунды, это 1962 год). После того, как карта была прочитана в буфер, устройство чтения карт отправляет буфер в ЦП и немедленно инициирует другую операцию загрузки буфера перед следующим запросом.
Если не только буфер пуст, но и нет карты в бункер, то мы должны дождаться, пока оператор поместит колоду в бункер и не нажмет Старт (что всегда инициирует операцию загрузки буфера).
В моей реализации запросы карты отправляются в устройство чтения карт в форма invokeLater ()
Runnables
в очереди на EDT. Во время myRunnable.run ()
либо будет доступен буфер (в этом случае мы можем отправить его в ЦП и запустить другую операцию загрузки буфера), либо буфер будет пуст. Что, если он пуст?
Две возможности: (а) уже выполняется операция загрузки буфера или (б) лоток для карт пуст (или еще не запущен). В любом случае недопустимо заставлять EDT ждать. Работа (и ожидание) должна выполняться в фоновом потоке.
Для простоты я попытался создать SwingWorker в ответ на каждый запрос карты, независимо от состояния буфера. Псевдокодом был:
SwingWorker worker = new SwingWorker() {
public Void doInBackground() throws Exception {
if (buffer.isEmpty()) {
/*
* fill() takes 1/4 second (simulated by Thread.sleep)
* or possibly minutes if we need to have another
* card deck mounted by operator.
*/
buffer.fill();
}
Card card = buffer.get(); // empties buffer
/*
* Send card to CPU
*/
CPU.sendMessage(card); // <== (A) put card in msg queue
/*
* Possible race window here!!
*/
buffer.fill(); // <== (B) pre-fetch next card
return null;
}
};
worker.execute();
Это произвело некоторые странные эффекты синхронизации - я подозреваю, из-за гонки buffer.fill ()
, которая могла произойти следующим образом: если между (A) и (B), ЦП получил карту, отправил запрос на другую и от своего имени создал еще один поток SwingWorker, тогда могут быть два потока одновременно, пытающиеся заполнить буфер. [Удаление вызова предварительной выборки в (B) решило эту проблему.]
Поэтому я считаю, что создание потока SwingWorker для каждого чтения неверно. Буферизация и отправка карт должны быть сериализованы в одном потоке. Этот поток должен попытаться выполнить предварительную выборку буфера и должен иметь возможность ждать и возобновлять работу, если у нас заканчиваются карты и мы должны ждать, пока в загрузочный лоток будут помещены новые. Я подозреваю, что SwingWorker имеет то, что требуется для того, чтобы быть долго работающим фоновым потоком, чтобы справиться с этим, но я еще не совсем там.
Предполагая, что поток SwingWorker - это путь, как я могу реализовать это, устраняя задержку на EDT, позволяя потоку блокироваться в ожидании пополнения бункера и обрабатывать неопределенность того, завершится ли заполнение буфера до или после получения другого запроса карты?
РЕДАКТИРОВАТЬ: Я получил ответ от другого потока и резюмирую здесь:
Вместо использования потока SwingWorker было рекомендовано создать ExecutorService
newSingleThreadExecutor ()
один раз, в начале, и поместить в него длинные методы графического интерфейса пользователя. используя execute (Runnable foo)
, как показано ниже (этот код работает в EDT):
private ExecutorService executorService;
::
/*
* In constructor: create the thread
*/
executorService = Executors.newSingleThreadExecutor();
::
/*
* When EDT receives a request for a card it calls readCard(),
* which queues the work out to the *single* thread.
*/
public void readCard() throws Exception {
executorService.execute(new Runnable() {
public void run() {
if (buffer.isEmpty()) {
/*
* fill() takes 1/4 second (simulated by Thread.sleep)
* or possibly minutes if we need to have another
* card deck mounted by operator.
*/
buffer.fill();
}
Card card = buffer.get(); // empties buffer
/*
* Send card to CPU
*/
CPU.sendMessage(card); // <== (A) put card in msg queue
/*
* No race! Next request will run on same thread, after us.
*/
buffer.fill(); // <== (B) pre-fetch next card
return;
}
});
}
Основное различие между этим и SwingWorker заключается в том, что это гарантирует наличие только одного рабочего потока.