Диалоговые панели
Диалоговые панели
Исходный текст приложения MenuApp
Исходный текст приложения MenuApp
Исходный текст приложения MenuApp представлен в листинге 1.
Использование класса Dialog
Использование класса Dialog
Для того чтобы создать свою диалоговую панель, вы должны определить новый класс, унаследовав его от класса Dialog, как это показано ниже:
class MessageBox extends Dialog { . . . public MessageBox(String sMsg, Frame parent, String sTitle, boolean modal) { super(parent, sTitle, modal); . . . resize(200, 100); . . . } }В этом классе нужно определить конструктор, который вызывает конструктор базового метода класса Dialog и определяет размеры окна диалоговой панели. Кроме того, в конструкторе вы должны создать все необходимые компоненты для размещения внутри диалоговой панели (кнопки, списки, текстовые поля, переключатели и так далее), а также выполнить размещение этих компонент, установив нужный режим размещения.
Для окон класса Dialog устанавливается режим размещения BorderLayout. Если нужен другой режим размещения, необходимо установить его явным образом методом setLayout.
Для отображения окна диалоговой панели необходимо вызвать метод show. Чтобы спрятать диалоговой окно, применяйте метод hide. Метод dispose удаляет окно диалоговой панели окончательно и освобождает все связанные с ним ресурсы.
Когда пользователь пытается уничтожить окно диалоговой панели при помощи органов управления, расположенных в заголовке такого окна, возникает событие Event.WINDOW_DESTROY. Вы должны обработать его, обеспечив удаление окна диалоговой панели вызовом метода dispose, если, конечно, это соответствует логике работы вашей панели.
Класс MainFrameWnd
Класс MainFrameWnd
Класс MainFrameWnd создан на базе класса Frame:
class MainFrameWnd extends Frame { . . . }В нем мы определили три поля, конструктор, методы paint, handleEvent и action.
Класс Menu
Класс Menu
Для того чтобы дать вам представление о том, что можно делать с меню, приведем краткое описание класса Menu:
Класс MenuApp
Класс MenuApp
В главном классе приложения MenuApp мы определили только один метод main. Этот метод получает управление при запуске приложения.
Первым делом метод main создает объект класса MainFrameWnd, определенного в нашем приложении:
MainFrameWnd frame = new MainFrameWnd("MenuApp");Этот класс, созданный на базе класса Frame, определяет поведение главного окна нашего приложения.
На втором шаге метод init настраивает размеры главного окна с учетом размеров внешней рамки и заголовка окна:
frame.setSize(frame.getInsets().left + frame.getInsets().right + 320, frame.getInsets().top + frame.getInsets().bottom + 240);Поля left и right объекта класса Insets, ссылку на который возвращает метод getInsets, содержат ширину левой и правой части рамки окна, соответственно. Поле top содержит высоту верхней части рамки окна с учетом заголовка, а поле bottom - высоту нижней части рамки окна.
Для отображения окна фрейма мы вызываем метод show, как это показано ниже:
frame.show();Класс MenuItem
Класс MenuItem
Класс MenuItem определяет поведение отдельных элементов меню.
Пользуясь методами класса MenuItem вы можете блокировать или разблокировать отдельные строки меню. Это нужно делать, например, если в данный момент функция, соответствующая строке меню, недоступна или не определена. Вы также можете изменять текстовые строки, соответствующие элементам меню, что может пригодиться для переопределения их назначения.
Класс MessageBox
Класс MessageBox
Для отображения названий выбранных строк меню мы создаем диалоговую панель, определив свой класс MessageBox на базе класса Dialog, как это показано ниже:
class MessageBox extends Dialog { . . . }В классе MessageBox есть два поля, конструктор, методы handleEvent и action.
Конструктор класса MainFrameWnd
Конструктор класса MainFrameWnd
В качестве единственного параметра конструктору класса MainFrameWnd передается заголовок создаваемого окна. В первой исполняемой строке наш конструктор вызывает конструктор из базового класса, передавая ему строку заголовка через параметр:
public MainFrameWnd(String sTitle) { super(sTitle); . . . }Далее конструктор определяет размеры окна, вызывая для него метод setSize:
setSize(400, 200);Затем мы устанавливаем для нашего окна желтый цвет фона и черный цвет изображения:
setBackground(Color.yellow); setForeground(Color.black);По умолчанию для окон класса Frame устанавливается режим добавления компонент BorderLayout. Мы изменяем этот режим на FlowLayout, вызывая метод setLayout:
setLayout(new FlowLayout());Далее конструктор приступает к формированию главного меню окна. Это меню создается как объект класса MenuBar:
mbMainMenuBar = new MenuBar();Затем мы создаем и наполняем меню "File":
mnFile = new Menu("File"); mnFile.add("New"); mnFile.add("-"); mnFile.add("Exit");Это меню создается на базе класса Menu. Обратите внимание, что между строками New и File расположен разделитель.
Аналогичным образом мы добавляем в главное меню другое меню - "Help":
mnHelp = new Menu("Help"); mnHelp.add("Content"); mnHelp.add("-"); mnHelp.add("About");После своего окончательного формирования меню "File" и "Help" добавляются в главное меню окна mbMainMenuBar:
mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp);И, наконец, когда главное меню будет сформировано, оно подключается к окну вызовом метода setMenuBar, как это показано ниже:
setMenuBar(mbMainMenuBar);Конструктор класса MessageBox
Конструктор класса MessageBox
Наш конструктор создает диалоговую панель с заданным сообщением внутри нее. Ссылка на строку сообщения передается конструктору через первый параметр. Остальные параметры используются конструктором базового класса Dialog для создания диалоговой панели:
super(parent, sTitle, modal);После вызова конструктора из базового класса наш конструктор устанавливает размеры окна созданной диалоговой панели, вызывая метод resize:
resize(200, 100);Отменяя установленный по умолчанию режим размещения компонент BorderLayout, конструктор устанавливает режим GridLayout:
setLayout(new GridLayout(2, 1));Окно диалоговой панели при этом разделяется на две части по горизонтали. В верхнюю часть добавляется текстовое поле для отображения сообщения, в нижнюю - кнопка OK:
lbMsg = new Label(sMsg, Label.CENTER); add(lbMsg); btnOK = new Button("OK"); add(btnOK);Конструкторы
Конструкторы
Для класса Frame определено два конструктора:
Создание окна без заголовка
public Frame();Создание окна с заголовоком
public Frame(String title);Конструкторы
Конструкторы
Создание меню с заданным названием public Menu(String label);
Создание меню с заданным названием,которое может оставаться на экране после того как пользователь отпустил клавишу мыши
public Menu(String label, boolean tearOff);Конструкторы
Конструкторы
Создание диалоговой панели без заголовка
public Dialog(Frame parent, boolean modal);Создание диалоговой панели с заголовком
public Dialog(Frame parent, String title, boolean modal);MenuBar mbMainMenuBar; Menu mnFile; Menu
Листинг 1
. Файл MenuApp.java import java.awt.*; public class MenuApp { public static void main(String args[]) { MainFrameWnd frame = new MainFrameWnd("MenuApp");
frame.setSize( frame.getInsets().left + frame.getInsets().right + 320, frame.getInsets().top + frame.getInsets().bottom + 240);
frame.show();
} } class MainFrameWnd extends Frame { MenuBar mbMainMenuBar; Menu mnFile; Menu mnHelp; public MainFrameWnd(String sTitle) { super(sTitle);
setSize(400, 200);
setBackground(Color.yellow);
setForeground(Color.black);
setLayout(new FlowLayout());
mbMainMenuBar = new MenuBar();
mnFile = new Menu("File");
mnFile.add("New");
mnFile.add("-");
mnFile.add("Exit");
mnHelp = new Menu("Help");
mnHelp.add("Content");
mnHelp.add("-");
mnHelp.add("About");
mbMainMenuBar.add(mnFile);
mbMainMenuBar.add(mnHelp);
setMenuBar(mbMainMenuBar);
} public void paint(Graphics g) { g.setFont(new Font( "Helvetica", Font.PLAIN, 12));
g.drawString("Frame window", 10, 70);
super.paint(g);
} public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { setVisible(false);
System.exit(0);
return true; } else return super.handleEvent(evt);
} public boolean action(Event evt, Object obj) { MenuItem mnItem; if(evt.target instanceof MenuItem) { mnItem = (MenuItem)evt.target; if(obj.equals("Exit")) { System.exit(0);
} else if(obj.equals("New")) { MessageBox mbox; mbox = new MessageBox( "Item New selected", this, "Dialog from Frame", true);
mbox.show();
} else if(obj.equals("Content")) { MessageBox mbox; mbox = new MessageBox( "Item Content selected", this, "Dialog from Frame", true);
mbox.show();
} else if(obj.equals("About")) { MessageBox mbox; mbox = new MessageBox( "Item About selected", this, "Dialog from Frame", true);
mbox.show();
} else return false; return true; } return false; } } class MessageBox extends Dialog { Label lbMsg; Button btnOK; public MessageBox(String sMsg, Frame parent, String sTitle, boolean modal) { super(parent, sTitle, modal);
resize(200, 100);
setLayout(new GridLayout(2, 1));
lbMsg = new Label(sMsg, Label.CENTER);
add(lbMsg);
btnOK = new Button("OK");
add(btnOK);
} public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { dispose();
return true; } else return super.handleEvent(evt);
} public boolean action(Event evt, Object obj) { Button btn; if(evt.target instanceof Button) { btn = (Button)evt.target; if(evt.target.equals(btnOK)) { dispose();
} else return false; return true; } return false; } }
Меню MenuBar
Меню MenuBar
Меню в окне класса Frame
Меню в окне класса Frame
Как мы уже говорили, окно класса Frame может иметь главное меню (Menu Bar) или, как еще говорят, строку меню. Главное меню создается на базе класса MenuBar, краткое описание которого приведено ниже.
Метод action класса MessageBox
Метод action класса MessageBox
Если пользователь нажимает кнопку OK, расположенную в окне диалоговой панели, метод action вызывает для панели метод dispose, удаляя эту панель с экрана и из памяти:
if(evt.target.equals(btnOK)) { dispose(); }Метод action
Метод action
Этот метод обрабатывает события, возникающие при выборе строка из меню.
В начале своей работы метод action проверяет, действительно ли событие вызвано меню:
MenuItem mnItem; if(evt.target instanceof MenuItem) { . . . } return false;Если это так, в поле mnItem сохраняется ссылка на элемент меню, вызвавший событие:
mnItem = (MenuItem)evt.target;Тем не менее, для определения строки, выбранной пользователем, нам достаточно проанализировать второй параметр метода action:
if(obj.equals("Exit")) { System.exit(0); } else if(obj.equals("New")) { MessageBox mbox; mbox = new MessageBox( "Item New selected", this, "Dialog from Frame", true); mbox.show(); } else if(obj.equals("Content")) { . . . } else if(obj.equals("About")) { . . . }В данном случае второй параметр метода action будет представлять собой ссылку на строку, выбранную из меню, поэтому для определения выбранной строки мы можем выполнить простое сравнение методом equals.
Если пользователь выбрал из меню File строку Exit, мы вызываем метод System.exit, предназначенный для завершения работы виртуальной машины Java.
В том случае когда пользователь выбирает любую другую строку из меню, метод action создает диалоговую панель на базе определенного нами класса MessageBox. В этой диалоговой панели отображаетя название выбранной строки меню.
Заметим, что сразу после создания конструктором диалоговая панель не появляется на экране. Мы отображаем ее, вызывая метод show.
Метод handleEvent
Метод handleEvent
Для того чтобы определить реакцию окна на попытку пользователя закрыть окно с помощью органов управления, расположенных в заголовке окна, или другим способом, мы переопределили метод handleEvent.
При получении кода события Event.WINDOW_DESTROY (удаление окна) мы скрываем окно, вызывая метод setVisible с параметром false.
Затем с помощью статического метода exit класса System мы завершаем работу интерпретатора:
public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { setVisible(false); System.exit(0); return true; } else return super.handleEvent(evt); }Метод handleEvent класса MessageBox
Метод handleEvent класса MessageBox
Когда пользователь пытается закрыть окно диалоговой панели, например, сделав двойной щелчок левой клавишей мыши по системному меню или одиночный щелчок по кнопке удаления окна, возникает событие Event.WINDOW_DESTROY. Мы его обрабатываем следующим образом:
if(evt.id == Event.WINDOW_DESTROY) { dispose(); return true; } else return super.handleEvent(evt);Вызывая метод dispose, мы удаляем окно диалоговой панели и освобождаем все связанные с ним ресурсы.
Метод paint
Метод paint
Метод paint получает в качестве параметра ссылку на контекст отображения, пригодный для рисования в нашем окне. Пользуясь этим контекстом, мы устанавливаем шрифт текста и рисуем текстовую строку. Затем мы вызываем метод paint из базового класса Frame, на основе которого создан наш класс MainFrameWnd:
public void paint(Graphics g) { g.setFont(new Font( "Helvetica", Font.PLAIN, 12)); g.drawString("Frame window", 10, 70); super.paint(g); }Методы
Методы
addNotify
Вызов метода createFrame
public void addNotify();dispose
Удаление окна и освобождение связанных с ним ресурсов
public void dispose();getCursorType
Определение типа курсора
public int getCursorType(); getIconImageПолучение пиктограммы, установленной для окна
public Image getIconImage(); getMenuBarПолучение ссылки на главное меню
public MenuBar getMenuBar(); getTitleПолучение заголовка окна
public String getTitle(); isResizableОпределение возможности изменения размеров окна пользователем
public boolean isResizable(); paramStringПолучение строки параметров
protected String paramString(); removeУдаление компоненты меню
public void remove(MenuComponent m); setCursorУстановка типа курсора
public void setCursor(int cursorType); setIconImageУстановка пиктограммы
public void setIconImage(Image image); setMenuBarУстановка главного меню
public void setMenuBar(MenuBar mb); setResizableВключение или выключение возомжности изменения размеров окна
public void setResizable(boolean resizable); setTitleУстановка заголовка окна
public void setTitle(String title);Методы
Методы
add
Добавление меню в главное меню окна
public Menu add(Menu m); addNotifyВызов метода createMenuBar
public void addNotify(); countMenusОпределение количества меню, добавленных в главное меню
public int countMenus(); getHelpMenuПолучение ссылки на меню Help
public Menu getHelpMenu(); getMenuПолучение ссылки на меню с заданным номером
public Menu getMenu(int i); removeУдаление меню с заданным номером из главного меню
public void remove(int index);Удаление компоненты меню
public void remove(MenuComponent m); removeNotifyИзвещение об удалении меню
public void removeNotify(); setHelpMenuУстановка меню Help
public void setHelpMenu(Menu m);Методы
Методы
add
Добавление элемента меню
public MenuItem add(MenuItem mi);Добавление строки в меню
public void add(String label); addNotifyВызов метода createMenu
public void addNotify(); addSeparatorДобавление разделителя в меню
public void addSeparator(); countItemsОпределение количества строк в меню
public int countItems(); getItemПолучение ссылки на элемент меню с заданным номером
public MenuItem getItem(int index); isTearOffПроверка, остается ли меню на экране после того как пользователь отпустил клавишу мыши
public boolean isTearOff(); removeУдаление заданного элемента меню
public void remove(int index);Удаление заданной компоненты меню
public void remove(MenuComponent item); removeNotifyИзвещение об удалении меню
public void removeNotify();Методы
Методы
addNotify
Вызов метода createMenuItem
public void addNotify(); disableБлокирование элемента меню
public void disable(); enableРазблокирование элемента меню
public void enable();Блокирование или разблокирование элемента меню
public void enable(boolean cond); getLabelПолучение текстовой строки меню
public String getLabel(); isEnabledПроверка, является ли элемент меню заблокированным
public boolean isEnabled(); paramStringПолучение строки параметров
public String paramString(); setLabelУстановка текстовой строки для элемента меню
public void setLabel(String label);Методы
Методы
addNotify
Вызов метода createDialog
public void addNotify(); getTitleПолучение строки заголовка диалоговой панели
public String getTitle(); isModalОпределение, является ли диалоговая панель модальной
public boolean isModal(); isResizableОпределение возможности изменения размеров окна диалоговой панели
public boolean isResizable(); paramStringПолучение строки параметров
protected String paramString();setResizable
Включение или выключение возможности изменения размеров окна диалоговой панели
public void setResizable(boolean resizable); setTitleУстановка заголовка диалоговой панели
public void setTitle(String title);Окна и диалоговые панели
Окна и диалоговые панели
До сих пор мы рисовали только в окне аплета или в окнах панелей, расположенных внутри окна аплета. Однако есть и другая возможность - приложения Java, полноценные и аплеты, могут создавать обычные перекрывающиеся окна, такие, например, как окно браузера. Эти окна могут иметь меню (в отличие от окон аплетов). Пользователь может изменять размер таких окон при помощи мыши, перемещая рамку окна.
В составе библиотеки классов AWT имеется несколько классов, предназначенных для работы с окнами. Это класс Window, который произошел от класса Container, и его дочерние классы - Frame, Dialog и FileDialog (Рисунок 1).
Окна класса Frame
Окна класса Frame
Ниже мы привели краткое описание класса Frame. Так как этот класс реализует интерфейс java.awt.MenuContainer, окно класса Frame может содержать меню.
Описание исходного текста приложения MenuApp
Описание исходного текста приложения MenuApp
Как мы уже говорили, приложение MenuApp работает автономно. Поэтому мы импортируем только класс java.awt.*, необходимый для работы с окнами:
import java.awt.*;В нашем приложении определено три класса - MenuApp, MainFrameWnd и MessageBox.
Поля класса MainFrameWnd
Поля класса MainFrameWnd
Поле mbMainMenuBar предназанчено для хранения ссылки на главное меню приложения, создаваемое как объект класса MenuBar:
MenuBar mbMainMenuBar;Поля mnFile и mnHelp хранят ссылки на меню File и Help, соответственно:
Menu mnFile; Menu mnHelp;Данные меню создаются на базе класса Menu.
Поля класса MessageBox
Поля класса MessageBox
Внутри диалоговой панели мы расположили текстовое поле класса Label, предназначенное для отображения сообщения, и кнопку с надписью OK, с помощью которой можно завершить работу диалоговой панели.
Ссылка на текстовое поле хранится в поле lbMsg, на кнопку - в поле btnOK.
Поля
Поля
С помощью полей класса Frame вы можете задавать для своего окна различные типы курсоров:
public final static int CROSSHAIR_CURSOR; public final static int DEFAULT_CURSOR; public final static int E_RESIZE_CURSOR; public final static int HAND_CURSOR; public final static int MOVE_CURSOR; public final static int N_RESIZE_CURSOR; public final static int NE_RESIZE_CURSOR; public final static int NW_RESIZE_CURSOR; public final static int S_RESIZE_CURSOR; public final static int SE_RESIZE_CURSOR; public final static int SW_RESIZE_CURSOR; public final static int TEXT_CURSOR; public final static int W_RESIZE_CURSOR; public final static int WAIT_CURSOR;Приложение MenuApp
Приложение MenuApp
Автономное приложение MenuApp, работающее под управлением интерпертатора Java, демонстрирует способы создания меню. В его окне (Рисунок 1) имеется панель с меню File и Help.
Применение класса Frame
Применение класса Frame
Для того чтобы создать свое окно на базе класса Frame, вы должны определить свой класс, унаследовав его от класса Frame следующим образом:
class MainFrameWnd extends Frame { . . . public MainFrameWnd(String sTitle) { super(sTitle); . . . resize(400, 200); } . . . }Если мы будем создавать окно с заголовком, нам необходимо соответствующим образом определить конструктор класса этого окна. В частности, наш конструктор должен вызывать конструктор базового класса, передавая ему в качестве параметра строку заголовка окна. Напомним, что конструктор базового класса должен вызываться в конструкторе дочернего класса перед выполнением каких-либо других действий.
Обратите также внимание на вызов метода resize. Этот вызов необходим для задания размеров окна.
В конструкторе вы можете определить различные параметры создаваемого вами окна, например, указать форму курсора, пиктограмму, представляющую окно, задать меню, определить возможность изменения размеров окна и так далее. Мы остановимся подробнее на процедуре добавления меню к окну класса Frame, так как она требует пояснений. С изменением других характеристик окна вы справитесь самостоятельно.
При создании окна классов Frame и Dialog для них устанавливается режим размещения BorderLayout. Если вам нужен другой режим размещения, необходимо установить его явным образом.
Кроме того, созданное окно появится на экране только после вызова для него метода show.
Убрать окно с экрана вы можете методом hide. Этот метод прячет окно, но оставляет в памяти все связанные с ним ресурсы, поэтому вы сможете вновь отобразить спрятанное окно, вызвав метод show.
В отличие от метода hide, метод dispose удаляет окно и освобождает все связанные с ним ресурсы. Этот метод применяется для окончательного удаления окна с экрана и из памяти.
Еще одно замечание касается обработки операции уничтожения окна при помощи двойного щелчка левой клавиши мыши по системному меню окна или при помощи кнопки уничтожения окна, расположенной в правой части заголовка.
Когда пользователь пытается уничтожить окно класса Frame или Dialog подобным образом, возникает событие Event.WINDOW_DESTROY. Вы должны предусмотреть обработку этого события, выполняя действия, соответствующие логике работы вашего окна. Обычно окно уничтожается вызовом метода dispose, как это показано ниже:
public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { dispose(); return true; } else return super.handleEvent(evt); }Работа с классом Menu
Работа с классом Menu
Метод addSeparator используется для добавления в меню разделительной строки. Аналогичный результат достигается и при добавлении в меню стоки "-":
mnHelp.add("-");Заметим, что вы можете просто добавлять в меню строки по их названию, пользуясь методом add(String label), либо добавлять в меню элементы класса MenuItem, вызывая метод add(MenuItem mi).
Работа с классом MenuBar
Работа с классом MenuBar
Для формирования главного меню окна вы должны создать объект класса MenuBar с помощью конструктора, а затем добавить в него отдельные меню.
Объект главного меню создается следующим образом:
MenuBar mbMainMenuBar; mbMainMenuBar = new MenuBar();Отдельные меню создаются на базе класса Menu, например:
Menu mnFile; Menu mnHelp; mnFile = new Menu("File"); mnHelp = new Menu("Help");Создав меню, вы должны добавить в них строки. Для этого нужно вызвать метод add, передав ему в качестве параметра текст строки меню, например:
mnFile.add("New"); mnFile.add("-"); mnFile.add("Exit"); mnHelp.add("Content"); mnHelp.add("-"); mnHelp.add("About");Далее сформированные меню добавляются в главное меню:
mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp);И, наконец, теперь можно устанавливать главное меню в окне класса, созданного на базе класса Frame:
setMenuBar(mbMainMenuBar);Главное окно автономного приложения MenuApp
Рисунок 1. Главное окно автономного приложения MenuApp
В меню File мы добавили строки New и Exit, а также разделитель в виде горизонтальной линии (Рисунок 2).
Иерархия классов предназначенных для создания окон
Рисунок 1. Иерархия классов, предназначенных для создания окон
Окно, созданное на базе класса Frame, больше всего похоже на главное окно обычного приложения Windows. Оно может иметь главное меню, для него можно устанавливать форму курсора. Внутри такого окна можно рисовать. Так как окно класса Frame (так же как и другие окна AWT) произошли от класса Container, вы можете добавлять в них различные компоненты и панели, как мы это делали с окнами аплетов и панелей.
На базе класса Dialog создаются окна диалоговых панелей, очень похожих на обычные диалоговые панели Windows. Такие панели не могут иметь меню и обычно предназначены для запроса какой-либо информации у пользователя.
Класс FileDialog предназначен для создания диалоговых панелей диалоговые панели, с помощью которых можно выбирать файлы на локальных дисках компьютера.
Что же касается класса Window, то непосредственно этот класс редко применяется для создания окон, так как классы Frame, Dialog и FileDialog более удобны и обеспечивают все необходимые возможности.
Меню File
Рисунок 2. Меню File
Меню Help (Рисунок 3) содержит строки Content и About. Между ними также имеется разделительная линия.
Меню Help
Рисунок 3. Меню Help
Если выбрать любую строку, кроме строки Exit из меню File, на экране появится диалоговая панель с названием выбранной строки и кнопкой OK (Рисунок 4).
Диалоговая панель которая
Рисунок 4. Диалоговая панель, которая появляется при выборе строки New из меню File
Выбор строки Exit из меню File приводит к завершению работы приложения MenuApp.
Создание диалоговых панелей
Создание диалоговых панелей
Диалоговые панели создаются на базе класса Dialog, краткое описание которого приведено ниже.
Аплет Rectangles
Аплет Rectangles
В качестве примера многопоточного приложения мы приведем аплет Rectangles (Рисунок 1). Он создает три потока. Первый поток рисует в окне аплета прямоугольники случайного размера и цвета, второй - эллипсы, а третий управляет потоком рисования эллипсов.
Блокировка на заданный период времени
Блокировка на заданный период времени
С помощью метода sleep можно заблокировать поток на заданный период времени:
try { Thread.sleep(500); } catch (InterruptedException ee) { . . . }В данном примере работа потока Thread приостанавливается на 500 миллисекунд. Заметим, что во время ожидания приостановленный поток не отнимает ресурсы процессора.
Так как метод sleep может создавать исключение InterruptedException, необходимо предусмотреть его обработку. Для этого мы использовали операторы try и catch.
Блокировка потока
Блокировка потока
Синхронизированный поток, определенный как метод типа synchronized, может переходить в заблокированное состояние автоматически при попытке обращения к ресурсу, занятому другим синхронизированным методом, либо при выполнении некоторых операций ввода или вывода. Однако в ряде случаев полезно иметь более тонкие средства синхронизации, допускающие явное использование по запросу приложения.
Исходные тексты аплета Rectangles
Исходные тексты аплета Rectangles
Исходные тексты аплета Rectangles приведены в листинге 1.
Конструктор класса DrawRectangles
Конструктор класса DrawRectangles
В качестве параметра конструктору передается ссылка на класс аплета. Конструктор использует эту ссылку для получения и сохранения в полях класса контекста отображения и размеров окна аплета:
public DrawRectangles(Applet Appl) { g = Appl.getGraphics(); dimAppWndDimension = Appl.getSize(); }Конструктор класса NotifyTask
Конструктор класса NotifyTask
Конструктор класса NotifyTask записывает в поле STask ссылку на задачу рисования эллипсов:
public NotifyTask(Thread SynchroTask) { STask = SynchroTask; }Конструкторы
Конструкторы
Создание нового объекта Thread
public Thread();Создвание нового объекта Thread с указанием объекта, для которого будет вызываться метод run
public Thread(Runnable target);Аналогично предыдущему, но дополнительно задается имя нового объекта Thread
public Thread(Runnable target, String name);Создание объекта Thread с указанием его имени
public Thread(String name);Создание нового объекта Thread с указанием группы потока и объекта, для которого вызывается метод run
public Thread(ThreadGroup group, Runnable target);Аналогично предыдущему, но дополнительно задается имя нового объекта Thread
public Thread(ThreadGroup group, Runnable target, String name);Создание нового объекта Thread с указанием группы потока и имени объекта
public Thread(ThreadGroup group, String name);public class Rectangles extends Applet
Листинг 1
. Файл Rectangles,java import java.applet.*; import java.awt.*; public class Rectangles extends Applet { DrawRectangles m_DrawRectThread = null; DrawEllipse m_DrawEllipseThread = null; NotifyTask m_NotifyTaskThread = null public String getAppletInfo() { return "Name: Rectangles"; } public void paint(Graphics g) { Dimension dimAppWndDimension = getSize();
g.setColor(Color.yellow);
g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
g.setColor(Color.black);
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
} public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this);
m_DrawRectThread.start();
} if (m_DrawEllipseThread == null) { m_DrawEllipseThread = new DrawEllipse(this);
m_DrawEllipseThread.start();
} if (m_NotifyTaskThread == null) { m_NotifyTaskThread = new NotifyTask(m_DrawEllipseThread);
m_NotifyTaskThread.start();
} } public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop();
m_DrawRectThread = null; } if (m_DrawEllipseThread == null) { m_DrawEllipseThread.stop();
m_DrawEllipseThread = null; } if (m_NotifyTaskThread != null) { m_NotifyTaskThread.stop();
m_NotifyTaskThread = null; } } } class DrawRectangles extends Thread { Graphics g; Dimension dimAppWndDimension; public DrawRectangles(Applet Appl) { g = Appl.getGraphics();
dimAppWndDimension = Appl.getSize();
} public void run() { while (true) { int x, y, width, height; int rColor, gColor, bColor; x = (int)(dimAppWndDimension.width * Math.random());
y = (int)(dimAppWndDimension.height * Math.random());
width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2; rColor = (int)(255 * Math.random());
gColor = (int)(255 * Math.random());
bColor = (int)(255 * Math.random());
g.setColor(new Color(rColor, gColor, bColor));
g.fillRect(x, y, width, height);
try { Thread.sleep(50);
} catch (InterruptedException e) { stop();
} } } } class DrawEllipse extends Thread { Graphics g; Dimension dimAppWndDimension; public DrawEllipse(Applet Appl) { g = Appl.getGraphics();
dimAppWndDimension = Appl.getSize();
} public synchronized void run() { while (true) { int x, y, width, height; int rColor, gColor, bColor; x = (int)(dimAppWndDimension.width * Math.random());
y = (int)(dimAppWndDimension.height * Math.random());
width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2; rColor = (int)(255 * Math.random());
gColor = (int)(255 * Math.random());
bColor = (int)(255 * Math.random());
g.setColor(new Color(rColor, gColor, bColor));
g.fillOval(x, y, width, height);
try { this.wait();
} catch (InterruptedException e) { } } } } class NotifyTask extends Thread { Thread STask; public NotifyTask(Thread SynchroTask) { STask = SynchroTask; } public void run() { while (true) { try { Thread.sleep(30);
} catch (InterruptedException e) { } synchronized(STask) { STask.notify();
} } } }
Метод run класса DrawEllipse
Метод run класса DrawEllipse
Класс DrawEllipse очень похож на только что рассмотренный класс DrawRectangles. Отличие есть только в финальном фрагменте метода run, который мы и рассмотрим.
Вместо задержки на 50 миллисекунд метод run из класса DrawEllipse переходит в состояние ожидания извещения, вызывая метод wait:
try { this.wait(); } catch (InterruptedException e) { }Это извещение создается управляющим потоком класса NotifyTask, к описанию которого мы переходим.
Метод run класса DrawRectangles
Метод run класса DrawRectangles
Программный код метода run работает в рамках отдельного потока. Он рисует в окне аплета закрашенные прямоугольники. Прямоугольники имеют случайные координаты, расположение и цвет.
Для того чтобы рисовать, необходимо получить контекст отображения. Этот контекст был получен конструктором класса DrawRectangles и может быть использован методом run.
Вооружившись контекстом отображения и размерами окна аплета, поток входит в бесконечный цикл рисования прямоугольников.
В качестве генератора случайных чисел мы используем метод random из класса Math, который при каждом вызове возвращает новое случайное число типа double, лежащее в диапазоне значений от 0.0 до 1.0.
Координаты по осям X и Y рисуемого прямоугольника определяются простым умножением случайного числа, полученного от метода random, соответственно, на ширину и высоту окна аплета:
x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random());Аналогично определяются размеры прямоугольника, однако чтобы прямоугольники не были слишком крупными, мы делим полученные значения на 2:
width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2;Так как случайное число имеет тип double, в обоих случаях мы выполняем явное преобразование результата вычислений к типу int.
Для случайного выбора цвета прямоугольника мы вычисляем отдельные цветовые компоненты, умножая значение, полученное от метода random, на число 255:
rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random());Полученные значения цветовых компонент используются в конструкторе Color для получения цвета. Этот цвет устанавливается в контексте отображения методом setColor:
g.setColor(new Color(rColor, gColor, bColor));Теперь все готово для рисования прямоугольника, которое мы выполняем при помощи метода fillRect:
g.fillRect(x, y, width, height);После рисования прямоугольника метод run задерживает свою работу на 50 миллисекунд, вызывая метод sleep:
try { Thread.sleep(50); } catch (InterruptedException e) { stop(); }Для обработки исключения InterruptedException, которое может возникнуть во время работы этого метода, мы предусмотрели блок try - catch. При возникновении указанного исключения работа потока останавливается вызовом метода stop.
Метод run класса NotifyTask
Метод run класса NotifyTask
Метод run класса NotifyTask периодически разблокирует поток рисования эллипсов, вызывая для этого метод notify в цилке с задержкой 30 миллисекунд. Обращение к объекту STask, который хранит ссылку на поток рисования эллипсов, выполняется с использованием синхронизации:
public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { } synchronized(STask) { STask.notify(); } } }Метод start класса Rectangles
Метод start класса Rectangles
Этот метод последовательно создает три потока и запускает их на выполнение:
if(m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } if(m_DrawEllipseThread == null) { m_DrawEllipseThread = new DrawEllipse(this); m_DrawEllipseThread.start(); } if(m_NotifyTaskThread == null) { m_NotifyTaskThread = new NotifyTask(m_DrawEllipseThread); m_NotifyTaskThread.start(); }В качестве параметра конструкторам классов DrawRectangles и DrawEllipse мы передаем ссылку на аплет Rectangles. Эта ссылка будет нужна для получения контекста отображения и рисования геометрических фигур.
Поток класса NotifyTask будет управлять работой потока DrawEllipse, поэтому мы передаем его конструктору ссылку на соответствующий объект m_DrawEllipseThread.
Метод stop класса Rectangles
Метод stop класса Rectangles
Когда пользователь покидает страницу сервера Web с аплетом, метод stop класса Rectangles последовательно останавливает gjnjrb рисования прямоугольников и эллипсов, а также управляющий поток:
if(m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } if(m_DrawEllipseThread == null) { m_DrawEllipseThread.stop(); m_DrawEllipseThread = null; } if(m_NotifyTaskThread != null) { m_NotifyTaskThread.stop(); m_NotifyTaskThread = null; }Методы класса Thread
Методы класса Thread
В классе Thread определены три поля, несколько конструкторов и большое количество методов, предназначенных для работы с потоками. Ниже мы привели краткое описание полей, конструкторов и методов.
С помощью конструкторов вы можете создавать потоки различными способами, указывая при необходимости для них имя и группу. Имя предназначено для идентификации потока и является необязательным атрибутом. Что же касается групп, то они предназначены для организации защиты потоков друг от друга в рамках одного приложения.
Методы класса Thread предоставляют все необходимые возможности для управления потоками, в том числе для их синхронизации.
Методы
Методы
activeCount
Текущее количество активных потоков в группе, к которой принадлежит поток
public static int activeCount(); checkAccessТекущему потоку разрешается изменять объект Thread
public void checkAccesss(); countStackFramesОпределение количества фреймов в стеке
public int countStackFrames(); currentThreadОпределение текущего работающего потока
public static Thread currentThread(); destroyПринудительное завершение работы потока
public void destroy(); dumpStackВывод текущего содержимого стека для отладки
public static void dumpStack(); enumerateПолучение всех объектов Tread данной группы
public static int enumerate(Thread tarray[]); getNameОпределение имени потока
public final String getName(); getPriorityОпределение текущего приоритета потока
public final int getPriority(); getThreadGroupОпределение группы, к которой принадлежит поток
public final ThreadGroup getThreadGroup(); interruptПрерывание потока
public void interrupt(); interruptedОпределение, является ли поток прерванным
public static boolean interrupted(); isAliveОпределение, выполняется поток или нет
public final boolean isAlive(); isDaemonОпределение, является ли поток демоном
public final boolean isDaemon(); isInterruptedОпределение, является ли поток прерванным
public boolean isInterrupted(); joinОжидание завершения потока
public final void join();Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах
public final void join(long millis);Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах и наносекундах
public final void join(long millis, int nanos); resumeЗапуск временно приостановленного потока
public final void resume(); runМетод вызывается в том случае, если поток был создан как объект с интерфейсом Runnable
public void run(); setDaemonУстановка для потока режима демона
public final void setDaemon(boolean on); setNameУстаовка имени потока
public final void setName(String name); setPriorityУстановка приоритета потока
public final void setPriority(int newPriority); sleepЗадержка потока на заднное время. Время задается в миллисекундах и наносекундах
public static void sleep(long millis);Задержка потока на заднное время. Время задается в миллисекундах и наносекундах
public static void sleep(long millis, int nanos); startЗапуск потока на выполнение
public void start(); stopОстановка выполнения потока
public final void stop();Аварийная остановка выполнения потока с заданным исключением
public final void stop(Throwable obj); suspendПриостановка потока
public final void suspend(); toStringСтрока, представляющая объект-поток
public String toString(); yieldПриостановка текущего потока для того чтобы управление было передано другому потоку
public static void yield();Многопоточность
Многопоточность
Наверное, сегодня уже нет необходимости объяснять, что такое многопоточность. Все современные операционные системы, такие как Windows 95, Windows NT, OS/2 или UNIX способны работать в многопоточном режиме, повышая общую производительность системы за счет эффективного распараллеливания выполняемых потоков. Пока один поток находится в состоянии ожидания, например, завершения операции обмена данными с медленным периферийным устройством, другой может продолжать выполнять свою работу.
Пользователи уже давно привыкли запускать параллельно несколько приложений для того чтобы делать несколько дел сразу. Пока одно из них занимается, например, печатью документа на принтере или приемом электронной почты из сети Internet, другое может пересчитывать электронную таблицу или выполнять другую полезную работу. При этом сами по себе запускаемые приложения могут работать в рамках одного потока - операционная система сама заботится о распределении времени между всеми запущенными приложениями.
Создавая приложения для операционной системы Windows на языках программирования С или С++, вы могли решать многие задачи, такие как анимация или работа в сети, и без использования многопоточности. Например, для анимации можно было обрабатывать сообщения соответствующим образом настроенного таймера.
Приложениям Java такая методика недоступна, так как в этой среде не предусмотрено способов периодического вызова каких-либо процедур. Поэтому для решения многих задач вам просто не обойтись без многопоточности.
Многопоточность и анимация
Многопоточность и анимация
Описание исходных текстов аплета Rectangles
Описание исходных текстов аплета Rectangles
В этом приложении мы создаем на базе класса Thread три класса. Первый из них предназначен для создания потока рисования прямоугольников, второй - для создания потока рисования закрашенных эллипсов, а третий - для управления потоком рисования эллипсов.
Что же касается основного класса аплета, то он унаследован, как обычно, от класса Applet и не реализует интерфейс Runnable:
public class Rectangles extends Applet { . . . }Описание текстов
Описание текстов
Ожидание извещения
Ожидание извещения
Если вам нужно организовать взаимодействие потоков таким образом, чтобы один поток управлял работой другого или других потоков, вы можете воспользоваться методами wait, notify и notifyAll, определенными в классе Object.
Метод wait может использоваться либо с параметром, либо без параметра. Этот метод переводит поток в состояние ожидания, в котором он будет находиться до тех пор, пока для потока не будет вызван извещающий метод notify, notifyAll, или пока не истечет период времени, указанный в параметре метода wait.
Как пользоваться методами wait, notify и notifyAll?
Метод, который будет переводиться в состояние ожидания, должен быть синхронизированным, то есть его следует описать как synchronized:
public synchronized void run() { while (true) { . . . try { this.wait(); } catch (InterruptedException e) { } } }В этом примере внутри метода run определен цикл, вызывающий метод wait без параметров. Каждый раз при очередном проходе цикла метод run переводится в состояние ожидания до тех пор, пока другой поток не выполнит извещение с помощью метода notify.
Ниже мы привели пример потока, вызывающией метод notify:
public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { } synchronized(STask) { STask.notify(); } } }Этот поток реализован в рамках отдельного класса, конструктору которого передается ссылка на поток, вызывающую метод wait. Эта ссылка хранится в поле STask.
Обратите внимание, что хотя сам метод run не синхронизированный, вызов метода notify выполняется в синхронизированном режиме. В качестве объекта синхронизации выступает поток, для которого вызывается метод notify.
Ожидание завершения потока
Ожидание завершения потока
С помощью метода join вы можете выполнять ожидание завершения работы потока, для которой этот метод вызван.
Существует три определения метода join:
public final void join(); public final void join(long millis); public final void join(long millis, int nanos);Первый из них выполняет ожидание без ограничения во времени, для второго ожидание будет прервано принудительно через millis миллисекунд, а для третьего - через millis миллисекунд и nanos наносекунд. Учтите, что реально вы не сможете указывать время с точностью до наносекунд, так как дискретность системного таймера компьютера намного больше.
Поля класса DrawRectangles
Поля класса DrawRectangles
Класс DrawRectangles определен для потока рисования прямоугольников:
class DrawRectangles extends Thread { . . . }В поле g класа хранится контекст отображения окна аплета, а в поле dimAppWndDimension - размеры этого окна:
Graphics g; Dimension dimAppWndDimension;Значения этих полей определяются конструктором класса по ссылке на главный класс аплета.
Поля класса NotifyTask
Поля класса NotifyTask
В классе NotifyTask мы определили одно поле STask класса Thread. Это поле которое хранит ссылку на поток, работой которого управляет данный класс:
class NotifyTask extends Thread { Thread STask; . . . }Поля класса Rectangles
Поля класса Rectangles
В классе Rectangles мы определили три поля с именами m_DrawRectThread, m_DrawEllipseThread и m_NotifyTaskThread:
DrawRectangles m_DrawRectThread = null; DrawEllipse m_DrawEllipseThread = null; NotifyTask m_NotifyTaskThread = nullЭти поля являются ссылками на классы, соответственно DrawRectangles, DrawEllipse и NotifyTask . Первый из них создан для рисования прямоугольников, второй - эллипсов, а третий - для управления потоком рисования эллипсов.
Указанные поля инициализируются занчением null, что соответствует неработающим или несозданным задачам.
Поля
Поля
Три статических поля предназначены для назначения приоритетов потокам. NORM_PRIORITY
Нормальный
public final static int NORM_PRIORITY; MAX_PRIORITYМаксимальный
public final static int MAX_PRIORITY; MIN_PRIORITYМинимальный
public final static int MIN_PRIORITY;Поток
Поток
Для каждого процесса операционная система создает один главный поток (thread ), который является потоком выполняющихся по очереди команд центрального процессора. При необходимости главный поток может создавать другие потоки, пользуясь для этого программным интерфейсом операционной системы.
Все потоки, созданные процессом, выполняются в адресном пространстве этого процесса и имеют доступ к ресурсам процесса. Однако поток одного процесса не имеет никакого доступа к ресурсам потока другого процесса, так как они работают в разных адресных пространствах. При необходимости организации взаимодействия между процессами или потоками, принадлежащими разным процессам, следует пользоваться системными средствами, специально предназначенными для этого.
Потокидемоны
Потоки-демоны
Вызвав для потока метод setDaemon, вы превращаете обычную поток в поток-демон. Такой поток работает в фоновом режиме независимо от породившего его потока. Если поток-демон создает другие потоки, то они также станут получат статус потока-демона.
Заметим, что метод setDaemon необходимо вызывать после создания потока, но до момента его запуска, то есть перед вызовом метода start.
С помощью метода isDaemon вы можете проверить, является поток демоном, или нет.
Применение многопоточности для анимации
Применение многопоточности для анимации
Одно из наиболее распространенных применений аплетов - это создание анимационных эффектов типа бегущей строки, мерцающих огней или аналогичных, привлекающих внимание пользователя. Для того чтобы достичь такого эффекта, необходим какой либо механизм, позволяющий выполнять перерисовку всего окна аплета или его части периодически с заданным временным интервалом.
Работа аплетов, так же как и обычных приложений операционной системы Windows, основана на обработке событий. Для классического приложения Windows событие - это приход сообщения в функцию окна. Основной класс аплета обрабатывает события, переопределяя те или иные методы базового класса Applet.
Проблема с периодическим обновлением окна аплета возникает из-за того, что в языке Java не предусмотрено никакого механизма для создания генератора событий, способного вызывать какой-либо метод класса аплета с заданным интервалом времени. Вы не можете поступить так, как поступали в этой ситуации, разрабатывая обычные приложения Windows - создать таймер и организовать обработку периодически поступающих от него сообщений WM_TIMER.
Напомним, что перерисовка окна аплета выполняется методом paint, который вызывается виртуальной машиной Java асинхронно по отношению к выполнению другого кода аплета.
Можно ли воспользоваться методом paint для периодической перерисовки окна аплета, организовав в нем, например, бесконечный цикл с задержкой?
К сожалению, так поступать ни в коем случае нельзя. Метод paint после перерисовки окна аплета должен сразу возвратить управление, иначе работа аплета будет заблокирована.
Единственный выход из создавшейся ситуации - создание потока (или нескльких потоков), которые будут выполнять рисование в окне аплета асинхронно по отношению к работе кода аплета. Например, вы можете создать поток, который периодически обновляет окно аплета, вызывая для этого метод repaint, или рисовать из потока непосредственно в окне аплета, получив предварительно для этого окна контекст отображения.
Приоритеты потоков в приложениях Java
Приоритеты потоков в приложениях Java
Если процесс создал несколько потоков, то все они выполняются параллельно, причем время центрального процессора (или нескольких центральных процессоров в мультипроцессорных системах) распределяется между этими потоками.
Распределением времени центрального процессора занимается специальный модуль операционной системы - планировщик. Планировщик по очереди передает управление отдельным потокам, так что даже в однопроцессорной системе создается полная иллюзия параллельной работы запущенных потоков.
Распределение времени выполняется по прерываниям системного таймера. Поэтому каждому потоку дается определенный интервал времени, в течении которого он находится в активном состоянии.
Заметим, что распределение времени выполняется для потоков, а не для процессов. Потоки, созданные разными процессами, конкурируют между собой за получение процессорного времени.
Каким именно образом?
Приложения Java могут указывать три значения для приоритетов потоков. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY.
По умолчанию вновь созданный поток имеет нормальный приоритет NORM_PRIORITY. Если остальные потоки в системе имеют тот же самый приоритет, то все потоки пользуются процессорным времени на равных правах.
При необходимости вы можете повысить или понизить приоритет отдельных потоков, определив для них значение приоритета, соответственно, MAX_PRIORITY или MIN_PRIORITY. Потоки с повышенным приоритетом выполняются в первую очередь, а с пониженным - только при отсутствии готовых к выполнению потоков, имеющих нормальный или повышенный приоритет.
Процесс
Процесс
Процесс (process) - это объект, который создается операционной системой, когда пользователь запускает приложение. Процессу выделяется отдельное адресное пространство, причем это пространство физически недоступно для других процессов. Процесс может работать с файлами или с каналами связи локальной или глобальной сети. Когда вы запускаете текстовый процессор или программу калькулятора, вы создаете новый процесс.
Процессы потоки и приоритеты
Процессы, потоки и приоритеты
Прежде чем приступить к разговору о многопоточности, следует уточнить некоторые термины.
Обычно в любой многопоточной операционной системе выделяют такие объекты, как процессы и потоки. Между ними существует большая разница, которую следует четко себе представлять.
Реализация интерфейса Runnable
Реализация интерфейса Runnable
Описанный выше способ создания потоков как объектов класса Thread или унаследованных от него классов кажется достаточнао естественным. Однако этот способ не единственный. Если вам нужно создать только один поток, работающую одновременно с кодом аплета, проще выбрать второй способ с использованием интерфейса Runnable.
Идея заключается в том, что основной класс аплета, который является дочерним по отношению к классу Applet, дополнительно реализует интерфейс Runnable, как это показано ниже:
public class MultiTask extends Applet implements Runnable { Thread m_MultiTask = null; . . . public void run() { . . . } public void start() { if (m_MultiTask == null) { m_MultiTask = new Thread(this); m_MultiTask.start(); } } public void stop() { if (m_MultiTask != null) { m_MultiTask.stop(); m_MultiTask = null; } } }Внутри класса необходимо определить метод run, который будет выполняться в рамках отдельного потока. При этом можно считать, что код аплета и код метода run работают одновременно как разные потоки.
Для создания потока используется оператор new. Поток создается как объект класса Thread, причем конструктору передается ссылка на класс аплета:
m_MultiTask = new Thread(this);При этом, когда поток запустится, управление получит метод run, определенный в классе аплета.
Как запустить поток?
Запуск выполняется, как и раньше, методом start. Обычно поток запускается из метода start аплета, когда пользователь отображает страницу сервера Web, содержащую аплет. Остановка потока выполняется методом stop.
Реализация многопоточности в Java
Реализация многопоточности в Java
должны воспользоваться классом java.lang.Thread. В этом классе определены все методы, необходимые для создания потоков, управления их состоянием и синхронизации.
Как пользоваться классом Thread?
Есть две возможности. Во-первых, вы можете создать свой дочерний класс на базе класса Thread. При этом вы должны переопределить метод run. Ваша реализация этого метода будет работать в рамках отдельного потока. Во-вторых, ваш класс может реализовать интерфейс Runnable. При этом в рамках вашего класса необходимо определить метод run, который будет работать как отдельный поток.
Второй способ особенно удобен в тех случаях, когда ваш класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом вам нужна многопоточность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи.
Окно аплета Rectangles
Рисунок 1. Окно аплета Rectangles
Расположение прямоугольников и эллипсов также выбирается случайно.
Синхронизация методов
Синхронизация методов
Возможность синхронизации как бы встроена в каждый объект, создаваемый приложением Java. Для этого объекты снабжаются защелками, которые могут быть использованы для блокировки потоков, обращающихся к этим объектам.
Чтобы воспользоваться защелками, вы можете объявить соответствующий метод как synchronized, сделав его синхронизированным:
public synchronized void decrement() { . . . }При вызове синхронизированного метода соответствующий ему объект (в котором он определен) блокируется для использования другими синхронизированными методами. В результате предотвращается одновременная запись двумя методами значений в область памяти, принадлежащую данному объекту.
Использование синхронизированных методов - достаточно простой способ синхронизации потоков, обращающихся к общим критическим ресурсам, наподобие описанного выше банковского счета.
Заметим, что не обязательно синхронизовать весь метод - можно выполнить синхронизацию только критичного фрагмента кода.
. . . synchronized(Account) { if(Account.check(3000000)) Account.decrement(3000000); } . . .Здесь синхронизация выполняется для объекта Account.
Синхронизация потоков
Синхронизация потоков
Многопоточный режим работы открывает новые возможности для программистов, однако за эти возможности приходится расплачиваться усложнением процесса проектирования приложения и отладки. Основная трудность, с которой сталкиваются программисты, никогда не создававшие ранее многопоточные приложения, это синхронизация одновременно работающих потоков.
Для чего и когда она нужна?
Однопоточная программа, такая, например, как программа MS-DOS, при запуске получает в монопольное распоряжение все ресурсы компьютера. Так как в однопоточной системе существует только один процесс, он использует эти ресурсы в той последовательности, которая соответствует логике работы программы. Процессы и потоки, работающие одновременно в многопоточной системе, могут пытаться обращаться одновременно к одним и тем же ресурсам, что может привести к неправильной работе приложений.
Поясним это на простом примере.
Пусть мы создаем программу, выполняющую операции с банковским счетом. Операция снятия некоторой суммы денег со счета может происходить в следующей последовательности: на первом шаге проверяется общая сумма денег, которая хранится на счету; если общая сумма равна или превышает размер снимаемой суммы денег, общая сумма уменьшается на необходимую величину; значение остатка записывается на текущий счет.
Если операция уменьшения текущего счета выполняется в однопоточной системе, то никаких проблем не возникнет. Однако представим себе, что два процесса пытаются одновременно выполнить только что описанную операцию с одним и тем же счетом. Пусть при этом на счету находится 5 млн. долларов, а оба процесса пытаются снять с него по 3 млн. долларов.
Допустим, события разворачиваются следующим образом: первый процесс проверяет состояние текущего счета и убеждается, что на нем хранится 5 млн. долларов; второй процесс проверяет состояние текущего счета и также убеждается, что на нем хранится 5 млн. долларов; первый процесс уменьшает счет на 3 млн. долларов и записывает остаток (2 млн. долларов) на текущий счет; второй процесс выполняет ту же самую операцию, так как после проверки считает, что на счету по-прежнему хранится 5 млн. долларов.
В результате получилось, что со счета, на котором находилось 5 млн. долларов, было снято 6 млн. долларов, и при этом там осталось еще 2 млн. долларов! Итого - банку нанесен ущерб в 3 млн. долларов.
Как же составить программу уменьшения счета, чтобы она не позволяла вытворять подобное?
Очень просто - на время выполнения операций над счетом одним процессом необходимо запретить доступ к этому счету со стороны других процессов. В этом случае сценарий работы программы должен быть следующим: процесс блокирует счет для выполнения операций другими процессами, получая его в монопольное владение; процесс проводит процедуру уменьшения счета и записывает на текущий счет новое значение остатка; процесс разблокирует счет, разрешая другим процессам выполнение операций.
Когда первый процесс блокирует счет, он становится недоступен другим процессам. Если второй процесс также попытается заблокировать этот же счет, он будет переведен в состояние ожидания. Когда первый процесс уменьшит счет и на нем останется 2 млн. долларов, второй процесс будет разблокирован. Он проверит остаток, убедится, что сумма недостаточна и не будет проводить операцию.
Таким образом, в многопоточной среде необходима синхронизация потоков при обращении к критическим ресурсам. Если над такими ресурсами будут выполняться операции в неправильной последовательности, это приведет к возникновению трудно обнаруживаемых ошибок.
В языке программирования Java предусмотрено несколько средств для синхронизации потоков, которые мы сейчас рассмотрим.
Создание дочернего класса на базе класса Thread
Создание дочернего класса на базе класса Thread
Рассмотрим первый способ реализации многопоточности, основанный на наследовании от класса Thread. При использовании этого способа вы определяете для потока отдельный класс, например, так:
class DrawRectangles extends Thread { . . . public void run() { . . . } }Здесь определен класс DrawRectangles, который является дочерним по отношению к классу Thread.
Обратите внимание на метод run. Создавая свой класс на базе класса Thread, вы должны всегда определять этот метод, который и будет выполняться в рамках отдельного потока.
Заметим, что метод run не вызывается напрямую никакими другими методами. Он получает управление при запуске потока методом start.
Как это происходит?
Рассмотрим процедуру запуска потока на примере некоторого класса DrawRectangles.
Вначале ваше приложение должно создать объект класса Thread:
public class MultiTask2 extends Applet { Thread m_DrawRectThread = null; . . . public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } } }Создание объекта выполняется оператором new в методе start, который получает управление, когда пользователь открывает документ HTML с аплетом. Сразу после создания поток запускается на выполнение, для чего вызывается метод start.
Что касается метода run, то если поток используется для выполнения какой либо периодической работы, то этот метод содержит внутри себя бесконечный цикл. Когда цикл завершается и метод run возвращает управление, поток прекращает свою работу нормальным, не аварийным образом. Для аварийного завершения потока можно использовать метод interrupt.
Остановка работающего потока выполняется методом stop. Обычно остановка всех работающих потоков, созданных аплетом, выполняется методом stop класса аплета:
public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } }Напомним, что этот метод вызывается, когда пользователь покидает страницу сервера Web, содержащую аплет.
Временная приостановка и возобновление работы
Временная приостановка и возобновление работы
Методы suspend и resume позволяют, соответственно, временно приостанавливать и возобновлять работу потока.
В следующем фрагменте кода поток m_Rectangles приостанавливает свою работу, когда курсор мыши оказывается над окном аплета:
public boolean mouseEnter(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.suspend(); } return true; }Работа потока возобновляется, когда курсор мыши покидает окно аплета:
public boolean mouseExit(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.resume(); } return true; }