2013-10-03 2 views
1

Я закончил графическое приложение. У меня есть четыре класса: Main, UserWindow, Task1, Task2. Main класс содержит булевую переменную buttonStartPressed. Метод Main запускает экземпляр класса UserWindow и ждет, пока пользователь нажмет кнопку «Пуск». Когда пользователь нажимает кнопку «Пуск» (в UserWindow), ActionListener присваивает truestatic boolean buttonStartPressed, а метод Main продолжается.Что такое обычный способ написания графического интерфейса в Java?

Main.java

public static void ...... 
static boolean buttonStartPressed = false; 

........... 

while (!buttonStartPress) { 
Thread.sleep(50); 
} 

Task1 t1 = new Task1(); 

..... 
} 
} 

Он отлично работает, однако мне не нравится цикл while. Я чувствую, что это не обычный способ написать приложение. Есть и другой способ: я мог бы объединить классы Main и UserWindow, и результат ActionListener (buttonPressed) стал началом Task1. Но, с другой стороны, я считаю, что классы Main и UserWindow должны быть отделены друг от друга.

+1

Вы считали учебник Swing? –

+1

И ваш вопрос ...? –

+1

Я не вижу, как причина, вызываемая для закрытия, применяется к этому вопросу. OP * * имеет минимальное понимание проблемы, так как он нашел способ ее решить. Он просто (по праву) сомневается, что его решение является хорошим. Я не чувствую, что на вопрос также не хватает информации. Как его намерение, так и его нынешнее решение четко объясняются. – barjak

ответ

2

Свинг (и почти каждый набор инструментов GUI) имеет выделенный поток. Этот поток, поток отправки событий, запускается, когда вы в первую очередь нуждаетесь в нем. Обычно это когда вы setVisible a JFrame. Этот поток представляет собой гигантский цикл, роль которого состоит в том, чтобы потреблять события ввода и перерисовывать события и соответственно выполнять некоторую логику.

В вашем случае у вас фактически есть два потока. Первая - это нить main, а вторая - EDT. Ваша main нить делает busy wait. Код вашего actionListener выполнен в EDT, как только пользователь нажмет кнопку.

Вы используете логическую переменную, чтобы обеспечить связь двух потоков. Использование некоторой общей памяти действительно является одним из возможных способов сделать inter-thread communication.

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

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

Таким образом, добавление volatile ключевого слова, ваше решение работает: у вас есть EDT, который с удовольствием делает свои вещи, и когда пользователь нажимает на кнопку, то main поток выполняет Task1. Первый вопрос, который вы задаете себе, - это: Task1 трудоемкая задача? Действительно, самым простым решением было бы запустить Task1 в EDT, вызвав его с actionListener. Будьте предупреждены, что выполнение некоторого кода в EDT зависает от GUI. Если Task1 длится менее 100 мс, пользователь даже не заметит замораживание, и нет смысла выполнять его в другом потоке. Если вы беспокоитесь о том, чтобы связать свой графический интерфейс с классом «задача», вам следует просто использовать шаблон наблюдателя для предотвращения прямой зависимости.

Если задача занимает много времени, и вы не хотите, чтобы ваш графический интерфейс зависал, вам необходимо использовать несколько потоков. Одним из решений является тот, который вы реализовали. Это немного ограничено, потому что у вас есть только одна из main нить, но она работает. Теперь ваша проблема заключается в том, чтобы эти потоки обменивались данными. По-настоящему общий шаблон в межпоточной связи - использовать blocking queue. Это также общая часть данных, но она предназначена для использования несколькими потоками. Один поток (EDT) пишет ему (add()), а другой читает из него (take()) и блокирует, пока что-то не было написано. Это может показаться излишним для вашего простого примера, но это очень удобный способ обмена данными между потоками. Объекты, записанные в очередь блокировки, могут быть любыми; например, они могут представлять команды для выполнения.

Более традиционный способ выполнения трудоемкой функции из графического интерфейса - создать или использовать выделенный поток, когда вам нужно. Это можно сделать с использованием низкоуровневых API (Thread) или с использованием более высоких уровней (ExecutorService), причем оба они довольно просты в использовании. Опять же, используйте шаблон наблюдателя, если вы хотите отделить действие GUI от создания потока.

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

+0

Большое вам спасибо. Я очень благодарен за это потрясающее объяснение !!! – Buras

3

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

Вам даже нужна переменная buttonStartPressed? Почему вам было бы интересно узнать, была ли она нажата, не является ли основная идея выполнять какое-либо действие при нажатии кнопки?

Вы должны создать Task1 в своем методе actionPerformed(), и в зависимости от того, что вы пытаетесь сделать, запустите поток, который выполнит задачу (или просто запустите ее в EDT, если это действительно быстро сделать, поэтому это не приведет к замораживанию графического интерфейса).

Смежные вопросы