Ограниченные свойства (contrained properties)

Кроме понятия связанных свойств, в JavaBeans есть понятие ограниченных свойств (contrained properties). Ограниченные свойства введены для того, чтобы была возможность запретить изменение свойства бина, если это необходимо. Т.е. бин будет как бы спрашивать разрешения у зарегистрированных слушателей на изменение данного свойства. В случае если слушатель не разрешает ему менять свойство, он генерирует исключение PropertyVetoException. Соответствен­но set-метод для ограниченного свойства должен иметь в своем описании throws PropertyVetoException, что заставляет перехватывать это исклю­чение в точке вызова данного set-метода. В результате прикладная программа, использующая этот бин, будет извещена, что ограниченное свойство не было изменено.

В остальном ограниченные свойства очень похожи на связанные свойства. Как и все свойства, они имеют get- и set-методы. Но для них set-методы могут генерировать исключение PropertyVetoException и имеют вид

public void <PropertyName>(ТипСвойства param) throws PropertyVetoException.

Второе отличие заключается в именах методов для регистрации/дерегист­рации слушателей. Вместо методов

addPropertyChangeListener() и

RemovePropertyChangeListener()

для ограниченных свойств применяются методы

addVetoableChangeListener(VetoableChangeListener v) и removeVetoableChangeListener(VetoableChangeListener v). Здесь VetoableChangeListener – интерфейс с одним методом

void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException().

По аналогии со вспомогательным классом PropertyChangeSupport, который используется при реализации связанных свойств, для ограниченных свойств в пакете java.beans есть вспомогательный класс VetoableChangeSupport. В нем реализованы алгоритмы, необходимые для поддержки событий ограниченных свойств.

В качестве примера вспомним класс SomeBean, рассмотренный ранее. Его свойство someProperty() реализовано как связанное. Переделаем пример и реализуем это свойство как ограниченное.

/* пример # 26 : bean-класс с ограниченным свойством : SomeBean.java */

importjava.beans.*;

public class SomeBean {

private String someProperty = null;

private VetoableChangeSupport vcs;

public SomeBean(){

vcs = newVetoableChangeSupport(this);

}

public void addVetoableChangeListener

(VetoableChangeListener pcl){

vcs.addVetoableChangeListener(pcl);

}

public void removeVetoableChangeListener

(VetoableChangeListener pcl){

pcs.removePropertyChangeListener(pcl);

}

public String getSomeProperty(){

return someProperty;

}

public void setSomeProperty(String value) throws

PropertyVetoException{

vcs.fireVetoableChange(“someProperty”, someProperty, value);

someProperty = value;

}

}

Как видно, принципиально ничего не изменилось. Только вместо PropertyChangeSupport использован VetoableChangeSupportи в описании set-метода добавлено throws PropertyVetoException. Теперь someProperty является ограниченным свойством, и зарегистрировавшийся слушатель может запретить его изменение.

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

В таких случаях обычно используют существующие события типа ActionEvent, хотя можно построить и свои события.

Задания к главе 13

Вариант А

1. Создать апплет. Поместить на него текстовое поле JTextField, кнопку JButton и метку JLabel. В метке отображать все введенные символы, разделяя их пробелами.

2. Поместить в апплет две панели JPanel и кнопку. Первая панель содержит поле ввода и метку “Поле ввода”; вторая – поле вывода и метку “Поле вывода”. Для размещения в окне двух панелей и кнопки “Скопировать” использовать менеджер размещения BorderLayout.

3. Изменить задачу 2 так, чтобы при нажатии на кнопку “Скопировать” текст из поля ввода переносился в поле вывода, а поле ввода очищалось.

4. Задача 2 модифицируется так, что при копировании поля ввода нужно, кроме собственно копирования, организовать занесение строки из поля ввода во внутренний список. При решении использовать коллекцию,
в частности ArrayList.

5. К условию задачи 2 добавляется еще одна кнопка с надписью “Печать”. При нажатии на данную кнопку весь сохраненный список должен быть выведен в консоль. При решении использовать коллекцию, в частности TreeSet.

6. Написать программу для построения таблицы значений функции Ограниченные свойства (contrained properties) - student2.ru Использовать метку JLabel, содержащую текст “Функция: Ограниченные свойства (contrained properties) - student2.ru ”; панель, включающую три текстовых поля JTextField, содержащих значения параметра, шага (например, 0.1) и количества точек. Начальное значение x=0. С каждым текстовым полем связана метка, содержащая его название. В приложении должно находиться текстовое поле со скроллингом, содержащее полученную таблицу.

7. Создать форму с набором кнопок так, чтобы надпись на первой кнопке при ее нажатии передавалась на следующую, и т.д.

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

9. В апплете изобразить прямоугольник (окружность, эллипс, линию). Направление движения объекта по экрану изменяется на противоположное щелчком по клавише мыши. При этом каждый второй щелчок меняет цвет фона.

10. Создать фрейм с изображением окружности. Длина дуги окружности изменяется нажатием клавиш от 1 до 9.

11. Создать фрейм с кнопками. Кнопки “вверх”, “вниз”, “вправо”, “влево” двигают в соответствующем направлении линию. При достижении границ фрейма линия появляется с противоположной стороны.

12. Создать фрейм и разместить на нем окружность (одну или несколько). Объект должен “убегать” от указателя мыши. При приближении на некоторое расстояние объект появляется в другом месте фрейма.

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

14. Изменить задачу 12 так, чтобы количество объектов зависело от размеров апплета и изменялось при “перетягивании” границы в любом направлении.

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

16. Промоделировать в апплете аналоговые часы (со стрелками) с кнопками для увеличения/уменьшения времени на час/минуту.

Вариант B

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

Тестовые задания к главе 13

Вопрос 13.1.

Какой менеджер размещения использует таблицу с ячейками равного размера?

1) FlowLayout;

2) GridLayout;

3) BorderLayout;

4) CardLayout.

Вопрос 13.2.

Дан код:

import java.awt.*;

public class Quest2 extends Frame{

Quest2(){

Button yes = new Button("YES");

Button no = new Button("NO");

add(yes);

add(no);

setSize(100, 100);

setVisible(true);

}

public static void main(String[] args){

Quest2 q = new Quest2();

} }

В результате будет выведено:

1) две кнопки, занимающие весь фрейм, YES – слева и NO – справа;

2) одна кнопка YES, занимающая целый фрейм;

3) одна кнопка NO, занимающая целый фрейм;

4) две кнопки наверху фрейма – YES и NO.

Вопрос 13.3.

Какое выравнивание устанавливается по умолчанию для менеджера размещений FlowLayout?

1) FlowLayout.RIGHT;

2) FlowLayout.LEFT;

3) FlowLayout.CENTER;

4) FlowLayout.LEADING;

5) указывается явно.

Вопрос 13.4.

Сколько кнопок будет размещено в приведенном ниже апплете?

import java.awt.*;public class Quest4 extends java.applet.Applet{ Button b = new Button("YES"); public void init(){ add(b); add(b); add(new Button("NO")); add(new Button("NO")); }}

1) одна кнопка с YES и одна кнопка NO;

2) одна кнопка с YES и две кнопки NO;

3) две кнопки с YES и одна кнопка NO;

4) две кнопки с YES и две кнопки NO.

Вопрос 13.5.

Объект JСheckBox объявлен следующим образом:

JCheckBox ob = new JCheckBox();

Какая из следующих команд зарегистрирует его в блоке прослушивания событий?

1) ob.addItemListener();

2) ob.addItemListener(this);

3) addItemListener(this);

4) addItemListener();

5) ни одна из приведенных.»

 
Глава 14

ПОТОКИ ВЫПОЛНЕНИЯ

Класс Thread и интерфейс Runnable

К большинству современных распределенных приложений (Rich Client)
и Web-приложений (Thin Client) выдвигаются требования одновременной поддержки многих пользователей, каждому из которых выделяется отдельный поток, а также разделения и параллельной обработки информационных ресурсов. Потоки – средство, которое помогает организовать одновременное выполнение нескольких задач, каждую в независимом потоке. Потоки представляют собой классы, каждый из которых запускается и функционирует самостоятельно, автономно (или относительно автономно) от главного потока выполнения программы. Существуют два способа создания и запуска потока: расширение класса Thread или реализация интерфейса Runnable.

// пример # 1 : расширение класса Thread: Talk.java

package chapt14;

public class Talk extends Thread {

public void run() {

for (int i = 0; i < 8; i++) {

System.out.println("Talking");

try {

// остановка на 400 миллисекунд

Thread.sleep(400);

} catch (InterruptedException e) {

System.err.print(e);

}

}

}

}

При реализации интерфейса Runnable необходимо определить его единственный абстрактный метод run(). Например:

/* пример # 2 : реализация интерфейса Runnable: Walk.java: WalkTalk.java */

package chapt14;

public class Walk implements Runnable {

public void run() {

for (int i = 0; i < 8; i++) {

System.out.println("Walking");

try {

Thread.sleep(400);

} catch (InterruptedException e) {

System.err.println(e);

}

}

}

}

package chapt14;

public class WalkTalk {

public static void main(String[] args) {

// новые объекты потоков

Talk talk = new Talk();

Thread walk = new Thread(new Walk());

// запуск потоков

talk.start();

walk.start();

//Walk w = new Walk(); // просто объект, не поток

// w.run(); //выполнится метод, но поток не запустится!

}

}

Использование двух потоков для объектов классов Talk и Walk приводит
к выводу строк: Talking Walking. Порядок вывода, как правило, различен при нескольких запусках приложения.

Жизненный цикл потока

Новый
Работоспособный
Неработоспособный
Пассивный

При выполнении программы объект класса Thread может быть в одном из четырех основных состояний: “новый”, “работоспособный”, “неработоспособный” и “пассивный”. При создании потока он получает состояние “новый” (NEW) и не выполняется. Для перевода потока из состояния “новый” в состояние “работоспособный” (RUNNABLE) следует выполнить метод start(), который вызывает метод run() – основной метод потока.

Рис. 14.1.Состояния потока

Поток может находиться в одном из состояний, соответствующих элементам статически вложенного перечисления Thread.State:

NEW – поток создан, но еще не запущен;

RUNNABLE – поток выполняется;

BLOCKED – поток блокирован;

WAITING – поток ждет окончания работы другого потока;

TIMED_WAITING– поток некоторое время ждет окончания другого потока;

TERMINATED — поток завершен.

Получить значение состояния потока можно вызовом метода getState().

Поток переходит в состояние “неработоспособный” (WAITING) вызовом методов wait(), suspend()(deprecated-метод)или методов ввода/вывода, которые предполагают задержку. Для задержки потока на некоторое время (в миллисекундах) можно перевести его в режим ожидания (TIMED_WAITING) с помощью методов sleep(long millis)и wait(long timeout), при выполнении которого может генерироваться прерывание InterruptedException. Вернуть потоку работоспособность после вызова метода suspend() можно методом resume()(deprecated-метод), а после вызова метода wait() – методами notify() или notifyAll(). Поток переходит в “пассивное” состояние (TERMINATED), если вызваны методы interrupt(), stop()(deprecated-метод)или метод run() завершил выполнение. После этого, чтобы запустить поток еще раз, необходимо создать новый объект потока. Метод interrupt()успешно завершает поток, если он находится в состоянии “работоспособность”. Если же поток неработоспособен, то метод генерирует исключительные ситуации разного типа в зависимости от способа остановки потока.

Интерфейс Runnable не имеет метода start(), а только единственный метод run(). Поэтому для запуска такого потока, как Walk, следует создать объект класса Thread и передать объект Walk его конструктору. Однако при прямом вызове методаrun()поток не запустится, выполнится только тело самого метода.

Методы suspend(), resume()и stop()являются deprecated-методами и запрещены к использованию, так как они не являются в полной мере “потоко-
безопасными”.

Управление приоритетами и группы потоков

Потоку можно назначить приоритет от 1(константа MIN_PRIORITY) до 10 (MAX_PRIORITY) с помощью метода setPriority(int prior). Получить значение приоритета можно с помощью метода getPriority().

// пример # 3 : демонстрация приоритетов: PriorityRunner.java: PriorThread.java

package chapt14;

public class PriorThread extends Thread {

public PriorThread(String name){

super(name);

}

public void run(){

for (int i = 0; i < 71; i++){

System.out.println(getName() + " " + i);

try {

sleep(1);//попробовать sleep(0);

} catch (InterruptedException e) {

System.err.print("Error" + e);

}

}

}

}

package chapt14;

public class PriorityRunner {

public static void main(String[] args) {

PriorThread min = new PriorThread("Min");//1

PriorThread max = new PriorThread("Max");//10

PriorThread norm = new PriorThread("Norm");//5

min.setPriority(Thread.MIN_PRIORITY);

max.setPriority(Thread.MAX_PRIORITY);

norm.setPriority(Thread.NORM_PRIORITY);

min.start();

norm.start();

max.start();

}

}

Поток с более высоким приоритетом в данном случае, как правило, монополизирует вывод на консоль.

Потоки объединяются в группы потоков. После создания потока нельзя изменить его принадлежность к группе.

ThreadGroup tg = new ThreadGroup("Группа потоков 1");

Thread t0 = new Thread(tg, "поток 0");

Все потоки, объединенные группой, имеют одинаковый приоритет. Чтобы определить, к какой группе относится поток, следует вызвать метод
getThreadGroup(). Если поток до включения в группу имел приоритет выше приоритета группы потоков, то после включения значение его приритета станет равным приоритету группы. Поток же со значением приоритета более низким, чем приоритет группы после включения в оную, значения своего приоритета не изменит.

Управление потоками

Приостановить (задержать) выполнение потока можно с помощью метода sleep(время задержки) класса Thread. Менее надежный альтернативный способ состоит в вызове метода yield(), который может сделать некоторую паузу и позволяет другим потокам начать выполнение своей задачи. Метод join() блокирует работу потока, в котором он вызван, до тех пор, пока не будет закончено выполнение вызывающего метод потока.

// пример # 4 : задержка потока: JoinRunner.java

package chapt14;

class Th extends Thread {

public Th(String str) {

super();

setName(str);

}

public void run() {

String nameT = getName();

System.out.println("Старт потока " + nameT);

if ("First".equals(nameT)) {

try {

sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("завершение потока "

+ nameT);

} else if ("Second".equals(nameT)) {

try {

sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("завершение потока "

+ nameT);

}

}

}

public class JoinRunner {

public static void main(String[] args) {

Th tr1 = new Th("First");

Th tr2 = new Th("Second");

tr1.start();

tr2.start();

try {

tr1.join();

System.out.println("завершение main");

} catch (InterruptedException e){

e.printStackTrace();

}

/* join() не дает работать потоку main до окончания выполнения потока tr1 */

}

}

Возможно, будет выведено:

Старт потока First

Старт потока Second

Завершение потока Second

Завершение потока First

Завершение main

Несмотря на вызов метода join()для потока tr1, поток tr2 будет работать, в отличие от потока main, который сможет продолжить свое выполнение только по завершении потока tr1.

Вызов метода yield() для исполняемого потока должен приводить к приостановке потока на некоторый квант времени, для того чтобы другие потоки могли выполнять свои действия. Однако если требуется надежная остановка потока, то следует использовать его крайне осторожно или вообще применить другой способ.

// пример # 5 : задержка потока: YieldRunner.java

package chapt14;

public class YieldRunner {

public static void main(String[] args) {

new Thread() {

public void run() {

System.out.println("старт потока 1");

Thread.yield();

System.out.println("завершение 1");

}

}.start();

new Thread() {

public void run() {

System.out.println("старт потока 2");

System.out.println("завершение 2");

}

}.start();

}

}

В результате может быть выведено:

Старт потока 1

Старт потока 2

Завершение 2

Завершение 1

Активизация метода yield() в коде метода run()первого объекта потока приведет к тому, что, скорее всего, первый поток будет остановлен на некоторый квант времени, что даст возможность другому потоку запуститься и выполнить свой код.

Потоки-демоны

Потоки-демоны работают в фоновом режиме вместе с программой, но не являются неотъемлемой частью программы. Если какой-либо процесс может выполняться на фоне работы основных потоков выполнения и его деятельность заключается в обслуживании основных потоков приложения, то такой процесс может быть запущен как поток-демон. С помощью метода setDaemon(boolean value), вызванного вновь созданным потоком до его запуска, можно определить поток-демон. Метод boolean isDaemon() позволяет определить, является ли указанный поток демоном или нет.

/* пример # 6 : запуск и выполнение потока-демона: DemoDaemonThread.java */

package chapt14;

class T extends Thread {

public void run() {

try {

if (isDaemon()){

System.out.println("старт потока-демона");

sleep(10000); // заменить параметр на 1

} else{

System.out.println("старт обычного потока");

}

} catch (InterruptedException e) {

System.err.print("Error" + e);

} finally {

if (!isDaemon())

System.out.println(

"завершение обычного потока");

Else

System.out.println(

"завершение потока-демона");

}

}

}

package chapt14;

public class DemoDaemonThread {

public static void main(String[] args) {

T usual = new T();

T daemon = new T();

daemon.setDaemon(true);

daemon.start();

usual.start();

System.out.println(

"последний оператор main");

}

}

В результате компиляции и запуска, возможно, будет выведено:

последний оператор main

Старт потока-демона

Старт обычного потока

завершение обычного потока

Поток-демон (из-за вызова метода sleep(10000)) не успел завершить выполнение своего кода до завершения основного потока приложения, связанного с методом main(). Базовое свойство потоков-демонов заключается в возможности основного потока приложения завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода метода main(), не обращая внимания на то, что поток-демон еще работает. Если уменьшать время задержки потока-демона, то он может успеть завершить свое выполнение до окончания работы основного потока.

Потоки в графических приложениях

Добавить анимацию в апплет можно при использовании потоков. Поток, ассоциированный с апплетом, следует запускать тогда, когда апплет становится видимым, и останавливать при сворачивании браузера. В этом случае метод
repaint() обновляет экран, в то время как программа выполняется. Поток создает анимационный эффект повторением вызова метода paint() и отображением графики в новой позиции.

/* пример # 7 : освобождение ресурсов апплетом: GraphicThreadsDemo.java */

package chapt14;

import java.awt.Color;

import java.awt.Container;

import java.awt.Graphics;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

public class GraphicThreadsDemo extends JFrame {

JPanel panel = new JPanel();

Graphics g;

JButton btn = new JButton("Добавить шарик");

int i;

public GraphicThreadsDemo() {

setBounds(100, 200, 270, 350);

Container contentPane = getContentPane();

contentPane.setLayout(null);

btn.setBounds(50, 10, 160, 20);

contentPane.add(btn);

panel.setBounds(30, 40, 200, 200);

panel.setBackground(Color.WHITE);

contentPane.add(panel);

btn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent ev) {

new BallThread(panel).start();

i++;

repaint();

}

});

}

public static void main(String[] args) {

GraphicThreadsDemo frame =

new GraphicThreadsDemo();

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true);

}

public void paint(Graphics g){

super.paint(g);

g.drawString("Количество шариков: " + i, 65, 300);

}

}

class BallThread extends Thread {

JPanel panel;

private int posX, posY;

private final int BALL_SIZE = 10;

private double alpha;

private int SPEED = 4;

BallThread(JPanel p) {

this.panel = p;

//задание начальной позиции и направления шарика

posX = (int)((panel.getWidth() - BALL_SIZE)

* Math.random());

posY = (int)((panel.getHeight() - BALL_SIZE)

* Math.random());

alpha = Math.random() * 10;

}

public void run() {

while(true) {

posX += (int)(SPEED * Math.cos(alpha));

posY += (int)(SPEED * Math.sin(alpha));

//вычисление угла отражения

if( posX >= panel.getWidth() - BALL_SIZE )

alpha = alpha + Math.PI - 2 * alpha;

else if( posX <= 0 )

alpha = Math.PI - alpha;

if( posY >= panel.getHeight() - BALL_SIZE )

alpha = -alpha;

else if( posY <= 0 )

alpha = -alpha;

paint(panel.getGraphics());

}

}

public void paint(Graphics g) {

//прорисовка шарика

g.setColor(Color.BLACK);

g.fillArc(posX, posY, BALL_SIZE, BALL_SIZE, 0, 360);

g.setColor(Color.WHITE);

g.drawArc(posX + 1, posY + 1, BALL_SIZE,

BALL_SIZE, 120, 30);

try {

sleep(30);

} catch(InterruptedException e) {

e.printStackTrace();

}

//удаление шарика

g.setColor(panel.getBackground());

g.fillArc(posX, posY, BALL_SIZE, BALL_SIZE, 0, 360);

}

}

Ограниченные свойства (contrained properties) - student2.ru

Рис.14.2.Потоки в апплетах

При вызове метода stop() апплета поток перестает существовать, так как ссылка на него устанавливается в nullи освобождает ресурсы. Для следующего запуска потока необходимо вновь инициализировать ссылку и вызвать метод start() потока.

Методы synchronized

Очень часто возникает ситуация, когда несколько потоков, обращающихся к некоторому общему ресурсу, начинают мешать друг другу; более того, они могут повредить этот общий ресурс. Например, когда два потока записывают информа­цию в файл/объект/поток. Для предотвращения такой ситуации может использоваться ключевое слово synchronized. Синхронизации не требуют только атомарные процессы по записи/чтению, не превышающие по объему 32 бит.

В качестве примера будет рассмотрен процесс записи информации в файл двумя конкурирующими потоками. В методе main() классa SynchroThreads создаются два потока. В этом же методе создается экземпляр класса Synchro, содержащий поле типа FileWriter, связанное с файлом на диске. Экземпляр Synchro передается в качестве параметра обоим потокам. Первый поток записывает строку методом writing() в экземпляр класса Synchro. Второй поток также пытается сделать запись строки в тот же самый объект Synchro. Для избежания одновременной записи такие методы объявляются как synchronized. Синхронизированный метод изолирует объект, после чего объект становится недоступным для других потоков. Изоляция снимается, когда поток полностью выполнит соответствующий метод. Другой способ снятия изоляции – вызов метода wait() из изолированного метода.

В примере продемонстрирован вариант синхронизации файла для защиты от одновременной записи информации в файл двумя различными потоками.

/* пример # 8 : синхронизация записи информации в файл : MyThread.java : Synchro.java : SynchroThreads.java */

package chapt14;

import java.io.*;

public class Synchro {

private FileWriter fileWriter;

public Synchro(String file) throws IOException {

fileWriter = new FileWriter(file, true);

}

public void close() {

try {

fileWriter.close();

} catch (IOException e) {

e.printStackTrace();

}

}

public synchronized void writing(String str, int i) {

try {

System.out.print(str + i);

fileWriter.append(str + i);

Thread.sleep((long)(Math.random() * 50));

System.out.print("->" + i + " ");

fileWriter.append("->" + i + " ");

} catch (IOException e) {

System.err.print("ошибка файла");

e.printStackTrace();

} catch (InterruptedException e) {

System.err.print("ошибка потока");

e.printStackTrace();

}

}

}

package chapt14;

public class MyThread extends Thread {

private Synchro s;

public MyThread(String str, Synchro s) {

super(str);

this.s = s;

}

public void run() {

for (int i = 0; i < 5; i++) {

s.writing(getName(), i);

}

}

}

package chapt14;

import java.io.*;

public class SynchroThreads {

public static void main(String[] args) {

try {

Synchro s = new Synchro("c:\\temp\\data.txt");

MyThread t1 = new MyThread("First", s);

MyThread t2 = new MyThread("Second", s);

t1.start();

t2.start();

t1.join();

t2.join();

s.close();

} catch (IOException e) {

System.err.print("ошибка файла");

e.printStackTrace();

} catch (InterruptedException e) {

System.err.print("ошибка потока");

e.printStackTrace();

}

}

}

В результате в файл будет выведено:

First0->0 Second0->0 First1->1 Second1->1 First2->2

Second2->2 First3->3 Second3->3 First4->4 Second4->4

Код построен таким образом, что при отключении синхронизации метода writing() при его вызове одним потоком другой поток может вклиниться и произвести запись своей информации, несмотря на то, что метод не завершил запись, инициированную первым потоком.

Вывод в этом случае может быть, например, следующим:

First0Second0->0 Second1->0 First1->1 First2->1 Second2->2 First3->3 First4->2 Second3->3 Second4->4 ->4

Инструкция synchronized

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

/* пример # 9 : блокировка объекта потоком: TwoThread.java */

packagechapt14;

public class TwoThread {

public static void main(String args[]) {

final StringBuffer s = new StringBuffer();

new Thread() {

public void run() {

int i = 0;

synchronized (s) {

while (i++ < 3) {

s.append("A");

try {

sleep(100);

} catch (InterruptedException e) {

System.err.print(e);

}

System.out.println(s);

}

}//конец synchronized

}

}.start();

new Thread() {

public void run() {

int j = 0;

synchronized (s) {

while (j++ < 3) {

s.append("B");

System.out.println(s);

}

}//конец synchronized

}

}.start();

}

}

В результате компиляции и запуска будет, скорее всего (так как и второй поток может заблокировать объект первым), выведено:

A

AA

AAA

AAAB

AAABB

AAABBB

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

Если в коде убрать синхронизацию объекта s, то вывод будет другим, так как другой поток сможет получить доступ к объекту и изменить его раньше, чем первый закончит выполнение цикла.

В следующем примере рассмотрено взаимодействие методов wait() и
notify() при освобождении и возврате блокировки в synchronized блоке. Эти методы используются для управления потоками в ситуации, когда необходимо задать определенную последовательность действий без повторного запуска потоков.

Метод wait(), вызванный внутри синхронизированного блока или метода, останавливает выполнение текущего потока и освобождает от блокировки захваченный объект, в частности объект lock. Возвратить блокировку объекта потоку можно вызовом метода notify() для конкретного потока или
notifyAll() для всех потоков. Вызов может быть осуществлен только из другого потока, заблокировавшего, в свою очередь, указанный объект.

/* пример # 10 : взаимодействие wait() и notify(): Blocked.java: Runner.java */

package chapt14;

public class Blocked {

private int i = 1000;

public int getI() {

return i;

}

public void setI(int i) {

this.i = i;

}

public synchronized void doWait() {

try {

System.out.print("Не ");

this.wait(); /* остановка потока и

освобождение блокировки*/

System.out.print("сущностей "); // после возврата блокировки

Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();

}

for (int j = 0; j < 5; j++) i/=5;

System.out.print("сверх ");

}

}

package chapt14;

public class Runner {

public static void main(String[] args) {

Blocked lock = new Blocked();

new Thread() {

public void run() {

lock.doWait();

}}.start();

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (lock) {// 1

lock.setI(lock.getI() + 2);

System.out.print("преумножай ");

lock.notify(); // возврат блокировки

}

synchronized (lock) {// 2

lock.setI(lock.getI() + 3);

//блокировка после doWait()

System.out.print("необходимого. ");

try {

lock.wait(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.print("=" + lock.getI());

}

}

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

Не преумножай сущностей сверх необходимого. =3

Задержки потоков методом sleep() используются для точной демонстрации последовательности действий, выполняемых потоками. Если же в коде приложения убрать все блоки синхронизации, а также вызовы методов wait() и notify(), то вывод может быть следующим:

Не сущностей преумножай необходимого. =1005сверх

Состояния потока

В классе Thread объявлено внутреннее перечисление State, простейшее применение элементов которого призвано помочь в отслеживании состояний потока в процессе функционирования приложения и, как следствие, в улучшении управления им.

/* пример # 11 : состояния NEW, RUNNABLE, TIMED_WAITING, TERMINATED : ThreadTimedWaitingStateTest.java */

package chapt14;

public class ThreadTimedWaitingStateTest extends Thread {

public void run() {

try{

Thread.sleep(50);

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

public static void main(String [] args){

try{

Thread thread = new ThreadTimedWaitingStateTest();

// NEW – поток создан, но ещё не запущен

System.out.println("1: " + thread.getState());

thread.start();

// RUNNABLE – поток запущен

System.out.println("2: " + thread.getState());

Thread.sleep(10);

// TIMED_WAITING

// поток ждет некоторое время окончания работы другого потока

System.out.println("3: " + thread.getState());

thread.join();

// TERMINATED – поток завершил выполнение

System.out.println("4: " + thread.getState());

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

}

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

NEW

RUNNABLE

TIMED_WAITING

TERMINATED

/* пример # 12 : состояния BLOCKED, WAITING : ThreadWaitingStateTest.java */

package chapt14;

public class ThreadWaitingStateTest extends Thread {

public void run() {

try {

synchronized (this) {

wait();

}

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

public static void main(String[] args) {

try {

Thread thread = new ThreadWaitingStateTest();

thread.start();

synchronized (thread) {

Thread.sleep(10);

// BLOCKED – because thread attempting to acquire a lock

System.out.println("1: " + thread.getState());

}

Thread.sleep(10);

// WAITING – метод wait() внутри synchronized

// останавил поток и освободил блокировку

System.out.println("2: " + thread.getState());

thread.interrupt();

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

}

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

BLOCKED

WAITING

Потоки в J2SE 5

Java всегда предлагала широкие возможности для мультипрограммирования: потоки – это основа языка. Однако очень часто использование таких возможностей становилось ловушкой: правильно написать и отладить многопоточную программу достаточно сложно.

В версии 1.5 языка добавлены пакеты классов java.util.concurrent.locks, java.util.concurrent.atomic,java.util.concurrent, возможности которых обеспечивают более высокую производительность, масштабируемость, построение потокобезопасных блоков параллельных (concurrent) классов, вызов утилит синхронизации, использование семафоров, ключей и atomic-переменных.

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

Ограниченно потокобезопасные (thread safe) коллекции и вспомогательные классы управления потоками сосредоточены в пакете java.util.concurrent. Среди них можно отметить:

· параллельные классы очередей ArrayBlockingQueue (FIFO очередь с фиксированой длиной), PriorityBlockingQueue (очередь с приоритетом) и ConcurrentLinkedQueue (FIFO очередь с нефиксированой длиной);

· параллельные аналоги существующих синхронизированых классов-коллекций ConcurrentHashMap (аналог Hashtable) и
CopyOnWriteArrayList (реализация List, оптимизированная для случая, когда количество итераций во много раз превосходит количество вставок и удалений);

· механизм управления заданиями, основанный на возможностях класса Executor, включающий пул потоков и службу их планирования;

· высокопроизводительный класс Lock, поддерживающий ограниченные ожидания снятия блокировки, прерываемые попытки блокировки, очереди блокировки и установку ожидания снятия нескольких блокиро­вок посредством класса Condition;

· классы атомарных переменных (AtomicInteger, AtomicLong,
AtomicReference), а также их высокопроизводительные аналоги SyncronizedInt и др.;

· классы синхронизации общего назначения, такие как Semaphore, CountDownLatch (позволяет потоку ожидать завершения нескольких операций в других потоках), CyclicBarrier (позволяет нескольким потокам ожидать момента, когда они все достигнут какой-либо точки) и Exchanger (позволяет потокам синхронизироваться и обмениваться информацией);

· обработка неотловленных прерываний: класс Thread теперь поддерживает установку обработчика на неотловленные прерывания (подобное ранее было доступно только в ThreadGroup).

Хорошим примером новых возможностей является синхронизация коллекции типа Hashtable. Объект Hashtable синхронизируется целиком, и если один поток занял кэш остальные потоки вынуждены ожидать освобождения объекта. В случае же использования нового класса ConcurrentHashMap практически все операции чтения могут проходить одновременно, операции чтения и записи практически не задерживают друг друга, и только одновременные попытки записи могут блокироваться. В случае же использования данного класса как кэша (спецификой которого является большое количество операций чтения и малое – записи), блокироваться многопоточный код практически не будет.

В таблице приведено время выполнения (в миллисекундах) программы, использовавшей в качестве кэша ConcurrentHashMap и Hashtable. Тесты проводились на двухпроцессорном сервере под управлением Linux. Количество данных для большего количества потоков увеличивалось.

Количество потоков ConcurrentHashMap Hashtable
1.00 1.03
2.59 32.40
5.58 78.23
13.21 163.48
27.58 341.21
57.27 778.41

// пример # 13 : применение семафора: Sort.java : ArraySort.java

package chapt14;

import java.util.concurrent.*;

public classSort {

public static final int ITEMS_COUNT = 15;

public static double items[];

// семафор, контролирующий разрешение на доступ к элементам массива

public static Semaphore sortSemaphore =

new Semaphore(0, true);

public static void main(String[] args) {

items = new double[ITEMS_COUNT];

for(int i = 0 ; i < items.length ; ++i)

items[i] = Math.random();

newThread(new ArraySort(items)).start();

for(int i = 0 ; i < items.length ; ++i) {

/*

* при проверке доступности элемента сортируемого

* массива происходит блокировка главного потока

* до освобождения семафора

*/

sortSemaphore.acquireUninterruptibly();

System.out.println(items[i]);

}

}

}

class ArraySort implements Runnable {

private double items[];

public ArraySort(double items[]) {

this.items = items;

}

public void run(){

for(int i = 0 ; i < items.length - 1 ; ++i) {

for(int j = i + 1 ; j < items.length ; ++j) {

if( items[i] < items[j] ) {

double tmp = items[i];

items[i] = items[j];

items[j] = tmp;

}

}

// освобождение семафора

Sort.sortSemaphore.release();

try {

Thread.sleep(71);

} catch (InterruptedException e) {

System.err.print(e);

}

}

Sort.sortSemaphore.release();

}

}

Задания к главе 14

Вариант А

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

2. Cоздать апплет с использованием потоков: строка движется по диагонали. При достижении границ апплета все символы строки случайным образом меняют регистр.

3. Организовать сортировку массива методами Шелла, Хоара, пузырька, на основе бинарного дерева в разных потоках.

4. Реализовать сортировку графических объектов, используя алгоритмы из задания 3.

5. Создать апплет с точкой, движущейся по окружности с постоянной угловой скоростью. Сворачивание браузера должно приводить к изменению угловой скорости движения точки для следующего запуска потока.

6. Изобразить точку, пересекающую с постоянной скоростью окно слева направо (справа налево) параллельно горизонтальной оси. Как только точка доходит до границы окна, в этот момент от левого (правого) края с вертикальной координатной y, выбранной с помощью датчика случайных чисел, начинает свое движение другая точка и т.д. Цвет точки также можно выбирать с помощью датчика случайных чисел. Для каждой точки создается собственный поток.

7. Изобразить в приложении правильные треугольники, вращающиеся
в плоскости экрана вокруг своего центра. Каждому объекту соответствует поток с заданным приоритетом.

8. Условие предыдущей задачи изменяется таким образом, что центр вращения перемещается от одного края окна до другого с постоянной скоростью параллельно горизонтальной оси.

9. Cоздать фрейм с тремя шариками, одновременно летающими в окне.
С каждым шариком связан свой поток со своим приоритетом.

10. Два изображения выводятся в окно. Затем они постепенно исчезают
с различной скоростью в различных потоках (случайным образом выбираются точки изображения, и их цвет устанавливается в цвет фона).

11. Условие предыдущей задачи изменить на применение эффекта постепенного “проявления” двух изображений.

Вариант B

Для заданий варианта В главы 4 организовать синхронизированный доступ к ресурсам (файлам). Для каждого процесса создать отдельный поток выполнения.

Тестовые задания к главе 14

Вопрос 14.1.

Дан код:

class Q implements Runnable{

int i = 0;

public int run(){

System.out.println("i = "+ ++i);

return i;

}}

public class Quest1 {

public static void main(String[] args) {

Q ob = new Q();

ob.run();

}}

При попытке компиляции и запуска будет выведено:

1) i = 0;

2) i = 1;

3) ошибка компиляции: создать объект потока можно только с помощью инициализации объекта класса Thread или его наследников;

4) ошибка компиляции: неправильно реализован метод run();

5) ошибка времени выполнения: поток должен запускаться методом start().

Вопрос 14.2.

Дан код:

Thread t1=new Thread();

t1.setPriority(7);

ThreadGroup tg=new ThreadGroup("TG");

tg.setMaxPriority(8);

Thread t2=new Thread(tg,"t2");

System.out.print("приоритет t1="

+ t1.getPriority());

System.out.print(", приоритет t2="

+ t2.getPriority());

В результате компиляции и выполнения будет выведено:

1) приоритет t1 = 7, приоритет t2 = 5;

2) приоритет t1 = 7, приоритет t2 = 8;

3) приоритет t1 = 10, приоритет t2 = 8;

4) нет правильного.

Вопрос 14.3.

Дан код:

class T1 implements Runnable{

public void run(){

System.out.print("t1 ");

} }

class T2 extends Thread{

public void run(){

System.out.print("t2 ");

} }

public class Quest3 {

public static void main(String[] args) {

T1 t1 = new T1();

T2 t2 = new T2(t1);

t1.start();

t2.start();

} }

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

1) t1 t2;

2) t2 t1;

3) ошибка компиляции: метод start() не определен в классе T1;

4) ошибка компиляции: в классе T2 не определен конструктор, принимающий в качестве параметра объект Thread;

5) ничего из перечисленного.

Вопрос 14.4.

Какое из указанных действий приведет к тому, что поток переходит в состояние “пассивный”? (выберите два)

1) вызов метода sleep() без параметра;

2) вызов метода stop();

3) окончание выполнения метода run();

4) вызов метода notifyAll();

5) вызов метода wait() с параметром null.

Вопрос 14.5.

Дан код:

class Quest5 extends Thread {Quest5 () { }Quest5 (Runnable r) { super(r); } public void run() { System.out.print("thread "); } public static void main(String[] args) { Runnable r = new Quest5(); //1Quest5 t = new Quest5(r); //2 t.start(); } }

При попытке компиляции и запуска будет выведено:

1) ошибка компиляции в строке //1;2) ошибка компиляции в строке //2;3) thread;4) thread thread; 5) код будет откомпилирован, но ничего выведено не будет.

 
Глава 15

СЕТЕВЫЕ ПРОГРАММЫ

Поддержка Интернет

Язык Java делает сетевое программирование простым благодаря наличию специальных средств и классов. Большинство этих классов находится в пакете java.net. Сетевые классы имеют методы для установки сетевых соединений передачи запросов и сообщений. Многопоточность позволяет обрабатывать несколько соединений. Сетевые приложения используют Internet-приложения, к которым относятся Web-браузер, e-mail, сетевые новости, передача файлов. Для создания таких приложений используются сокеты, порты, протоколы TCP/IP, UDP.

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

В стеке протоколов TCP/IP используются следующие прикладные протоколы:

HTTP - Hypertext Transfer Protocol (WWW);

NNTP - Network News Transfer Protocol (группы новостей);

SMTP - Simple Mail Transfer Protocol (посылка почты);

POP3 - Post Office Protocol (чтение почты с сервера);

FTP - File Transfer Protocol (протокол передачи файлов).

Каждый компьютер из подключенных к сети по протоколу TCP/IP имеет уникальный IP-адрес, используемый для идентификации и установки соединения. Это 32-битовое число, обычно записываемое как четыре числа, разделенные точками, каждое из которых изменяется от 0 до 255. IP-адрес может быть временным и выделяться динамически для каждого подключения или быть постоянным, как для сервера. IP-адреса используются во внутренних сетевых системах. Обычно при подключении к Internet вместо числового IP-адреса используются символьные имена (например: www.bsu.by), называемые именами домена. Специальная программа DNS (Domain Name Server), располагаемая на отдельном сервере, проверяет адрес и преобразует имя домена в числовой IP-адрес. Если в качестве сервера используется этот же компьютер без сетевого подключения, в качестве IP-адреса указывается 127.0.0.1 или localhost. Для явной идентификации услуг к IP-адресу присоединяется номер порта через двоеточие, например 217.21.43.10:443. Здесь указан номер порта 443. Номера портов от 1 до 1024 могут быть заняты для внутреннего использования, например, если порт явно не указан, браузер воспользуется значением по умолчанию: 20–FTP-данные,21–FTP-управление,53–DNS,80–HTTP,25–SMTP,110–POP3,119–NNTP. К серверу можно подключиться с помощью различных портов. Каждый порт указывает конкретное место соединения на указанном компьютере и предоставляет определенную услугу.

Для доступа к сети Internet в браузере указывается адрес URL. Адрес URL (Universal Resource Locator) состоит из двух частей – префикса протокола (http, https, ftp и т.д.) и URI (Universal Resource Identifier). URI содержит Internet-адрес, необязательный номер порта и путь к каталогу, содержащему файл, например:

http://www.bsu.by

URI не может содержать такие специальные символы, как пробелы, табуляции, возврат каретки. Их можно задавать через шестнадцатеричные коды. Например: %20 обозначает пробел. Другие зарезервированные символы: символ & –разделитель аргументов, символ ?–следует перед аргументами запросов, символ + – пробел, символ # – ссылки внутри страницы (имя_страницы#имя_ссылки).

Определить IP-адрес в приложеннии можно с помощью объекта класса InetAddress из пакета java.net.

Наши рекомендации