Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Metoda_OOP.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
1.71 Mб
Скачать
  • Сжатие данных в Java.

    7ЛАБОРАТОРНАЯ РАБОТА №7 Изучение многопоточности и работы с графикой

    Цель работы: Изучить основы работы с потоками Java. Научиться применять встроенные механизмы синхронизации потоков Java. Изучить основы работы с графикой Java2D.

    7.1 Задание на лабораторную работу

    В ходе выполнения лабораторной работы необходимо реализовать приложение с тремя вычислительными потоками. Для демонстрации работы потоков необходимо создать интерфейс пользователя, пример GUI приведен на рисунке 7.1

    Рисунок 7.1 – Пример пользовательского интерфейса

    Выполнение задания на лабораторную работу предполагает создание трех потоков:

    1. Создание основного потока для управления вычислительными процессами и GUI;

    2. Создание потока, в котором рассчитываются координаты точек, в зависимости от параметров задаваемых пользователем через GUI. Информация о точках помещается в очередь;

    3. Создание потока, в котором извлекаются данные из очереди и рисуются точки в отдельном фрейме;

    7.2 Краткие теоретические сведения

    7.2.1Многопоточность

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

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

    Создавая приложения для операционной системы Windows на языках программирования С или С++, вы могли решать многие задачи, такие как анимация или работа в сети, и без использования многопоточности. Например, для анимации можно было обрабатывать сообщения соответствующим образом настроенного таймера.

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

    7.2.2Процессы, потоки и приоритеты

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

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

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

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

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

    7.2.3Приоритеты потоков в приложениях Java

    Если процесс создал несколько потоков, то все они выполняются параллельно, причем время центрального процессора (или нескольких центральных процессоров в мультипроцессорных системах) распределяется между этими потоками.

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

    Распределение времени выполняется по прерываниям системного таймера. Поэтому каждому потоку дается определенный интервал времени, в течение которого он находится в активном состоянии.

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

    Приложения Java могут указывать три значения для приоритетов потоков. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY.

    По умолчанию вновь созданный поток имеет нормальный приоритет NORM_PRIORITY. Если остальные потоки в системе имеют тот же самый приоритет, то все потоки пользуются процессорным временем на равных правах.

    При необходимости вы можете повысить или понизить приоритет отдельных потоков, определив для них значение приоритета между MAX_PRIORITY и MIN_PRIORITY. Потоки с повышенным приоритетом выполняются в первую очередь, а с пониженным ‑ только при отсутствии готовых к выполнению потоков, имеющих нормальный или повышенный приоритет, т.е. они все равно выполняются, но значительно реже.

    7.2.4Реализация многопоточности в Java

    Модель потоков и ее поддержка в Java является программным механизмом для упрощения выполнения нескольких операций одновременно в одной и той же программе. Самый простой способ создать поток выполнения – унаследовать от класса java.lang.Thread, который снабжен всем необходимым для создания и запуска таких потоков, управления их состоянием и синхронизации. Наиболее важным методом является метод run( ), который необходимо переопределить. Именно метод run( ) содержит тот код, который будет выполняться параллельно с другими потоками программы.

    Есть три возможности:

    1. Можно создать свой дочерний класс на базе класса Thread.

    2. Можно реализовать интерфейс Runnable в своем классе, а затем создать объект класса Thread, передав конструктору этот объект как параметр.

    3. Можно создать дочерний класс на базе класса TimerTask, а затем создать объект класса Timer, установив время активации задания.

    Второй способ особенно удобен в тех случаях, когда класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом нужна многопоточность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи.

    7.2.5Функциональность класса Thread

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

    Методы класса Thread предоставляют все необходимые возможности для управления потоками, в том числе для их синхронизации.

    public final static int NORM_PRIORITY;

    Нормальный приоритет

    public final static int MAX_PRIORITY;

    Максимальный приоритет

    public final static int MIN_PRIORITY;

    Минимальный приоритет

    public Thread();

    Создание нового объекта Thread

    public Thread(Runnable target);

    Создание нового объекта Thread с указанием объекта, для которого будет вызываться метод run

    public Thread(Runnable target, String name);

    Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

    public Thread(String name);

    Создание объекта Thread с указанием его имени

    public Thread(ThreadGroup group, Runnable target);

    Создание нового объекта Thread с указанием группы потока и объекта, для которого вызывается метод run

    public Thread(ThreadGroup group,

    Runnable target, String name);

    Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

    public Thread(ThreadGroup group, String name);

    Создание нового объекта Thread с указанием группы потока и имени объекта

    public static int activeCount();

    Текущее количество активных потоков в группе, к которой принадлежит поток

    public void checkAccesss();

    Текущему потоку разрешается изменять объект Thread

    public int countStackFrames();

    Определение количества фреймов в стеке

    public static Thread currentThread();

    Определение текущего работающего потока

    public void destroy();

    Принудительное завершение работы потока

    public static void dumpStack();

    Вывод текущего содержимого стека для отладки

    public static int enumerate(Thread tarray[]);

    Получение всех объектов Tread данной группы

    public final String getName();

    Определение имени потока

    public final int getPriority();

    Определение текущего приоритета потока

    public final ThreadGroup getThreadGroup();

    Определение группы, к которой принадлежит поток

    public void interrupt();

    Прерывание потока

    public static boolean interrupted();

    Определение, является ли поток прерванным

    public final boolean isAlive();

    Определение, выполняется поток или нет

    public final boolean isDaemon();

    Определение, является ли поток демоном

    public boolean isInterrupted();

    Определение, является ли поток прерванным

    public final void join();

    Ожидание завершения потока

    public final void join(long millis);

    Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах

    public final void join(long millis, int nanos);

    Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах и наносекундах

    public final void resume();

    Запуск временно приостановленного потока

    public void run();

    Метод вызывается в том случае, если поток был создан как объект с интерфейсом Runnable

    public final void setDaemon(boolean on);

    Установка для потока режима демона

    public final void setName(String name);

    Установка имени потока

    public final void setPriority(int newPriority);

    Установка приоритета потока

    public static void sleep(long millis);

    Задержка потока на заданное время. Время задается в миллисекундах

    public static void sleep(long millis, int nanos);

    Задержка потока на заданное время. Время задается в миллисекундах и наносекундах

    public void start();

    Инициализация потока и запуск его на выполнение, вызывает run

    public final void stop();

    Остановка выполнения потока

    public final void stop(Throwable obj);

    Аварийная остановка выполнения потока с заданным исключением

    public final void suspend();

    Приостановка потока

    public String toString();

    Строка, представляющая объект-поток

    public static void yield();

    Приостановка текущего потока для того чтобы управление было передано другому потоку

    Жизненный цикл потока представлен на рисунке 7.2

    Рисунок 7.2 – Жизненный цикл потока

    В классе TimerTask определен абстрактный метод run, который и нужно определить в дочернем классе. Необходимо создать дочерний объект класса TimerTask, затем этот объект передается как параметр в метод schedule() класса Timer (см. JavaDOC).

    Пример ReminderBeep.java

    Для остановки таймера можно воспользоваться одним из следующих способов:

    • Вызвать метод cancel() класса TimerTask;

    • Установить поток связанный с таймером как демон ( использовать конструктор Timer(true) );

    • После выполнения задания, удалить все ссылки на объект Timer;

    • Вызвать метод System.exit().

    7.2.6Реализация интерфейса Runnable

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

    public class MultiTask extends Applet implements Runnable{

    Thread m_MultiTask = null;

    //другие поля . . .

    public void run()

    {

    Thread thisThread = Thread.currentThread();

    while (m_MultiTask == thisThread) {

    try {

    thisThread.sleep(interval);

    }

    catch (InterruptedException e){ }

    //код выполнения в отдельном потоке. . .

    //..repaint();

    }

    }

    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);

    m_MultiTask.start();

    Если необходимо создать поток, который работает независимо от потока диспетчера событий Abstract Window Toolkit (AWT), то можно воспользоваться статическим методом утилитного класса SwingUtilities invokeLater( Runnable doRun ). Поток запустится на выполнение после того, как обработаются все события из очереди java.awt.EventQue

    jButton.addActionListener(new java.awt.event.ActionListener() {

    public void actionPerformed(java.awt.event.ActionEvent e) {

    SwingUtilities.invokeLater(new Runnable(){

    public void run() {

    System.out.println("Hello from SwingUtilities");

    }

    });

    }

    });

    7.2.7Синхронизация потоков

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

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

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

    В языке программирования Java предусмотрено несколько средств для синхронизации потоков.

    7.2.8Синхронизация методов

    Для защиты критических участков программы используется ключевое слово

     

    synchronized(object){

    // critical operations}.

    При необходимости можно синхронизировать метод целиком

     

    public synchronized void decrement(){

    //тело метода

    }

    Поток переходит в заблокированное состояние автоматически при попытке обращения к ресурсу занятому другим потоком.

    7.2.9Блокировка потока

    static void sleep(long millis);

    static void sleep(long millis, int nanos);

    Блокировка на заданный промежуток времени. Выполнение потока приостанавливается на заданный промежуток времени, но блок не освобождается.

    final void wait();

    final void wait(long mills);

    final void wait(long mills, int nanos);

    Ожидание извещения. Выполнение потока приостанавливается до получения извещения notify(), notifyAll() либо до истечения периода ожидания.

    void join();

    void join(long mills);

    void join(longmills, int nanos);

    Ожидание завершения потока. Приостановление выполнения потока, до завершения работы потока, для которого был вызван этот метод либо до истечения периода ожидания.

    7.2.10Синхронизация доступа к совместно используемым данным.

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

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

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

    Язык Java гарантирует, что чтение и запись отдельной переменной, если это не переменная типа long или double, являются атомарными операциями. Хотя свойство атомарности гарантирует, что при чтении атомарных данных поток не увидит случайного значения, нет гарантии, что значение, записанное одним потоком, будет увидено другим. Это является следствием сугубо технического аспекта языка Java, который называется моделью памяти.

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

    /*

    * Требуется синхронизация

    */

    private static int nextSerialNumber = 0;

    public static int generateSerialNumber(){

    return nextSerialNumber++;

    }

    Без синхронизации этот метод не работает, оператор приращения (++) не является атомарным, он осуществляет чтение и запись в поле nextSerialNumber. Возможны различные варианты несогласованной работы метода при вызове из разных потоков. Возможны варианты когда один поток получит серию значений от 0 до n, а второй поток, вызвав метод generateSerialNumber, может получить номер равный нулю.  

    /*

    * Исправленный метод

    */

    private static int nextSerialNumber = 0;

    public static synchronized int generateSerialNumber(){

    return nextSerialNumber++;

    }

    Рассмотрим процедуру остановки потока. 

    /*

    * Требуется синхронизация

    */

    public class StoppableThread extends Thread {

    private boolean stopRequested = false;

    public void run(){

    boolean done = false;

    while (!stopRequested && !done) {

    //здесь выполняется необходимая обработка

    }

    }

    public void requestStop(){

    stopRequested = true;

    }

    }

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

    /*

    * Правильное синхронизированное совместное завершение потока

    */

     public class StoppableThread extends Thread {

    private boolean stopRequested = false;

    public void run(){

    boolean done = false;

    while (!stopRequested() && !done) {

    //здесь выполняется необходимая обработка

    }

    }

    public synchronized void requestStop(){

    stopRequested = true;

    }

    private synchronized boolean stopRequested(){

    return stopRequested;

    }

    }

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

    /*

    * Правильное совместное завершение потока

    */

    public class StoppableThread extends Thread {

    private volatile boolean stopRequested = false;

    public void run(){

    boolean done = false;

    while (!stopRequested && !done) {

    //здесь выполняется необходимая обработка

    }

    }

    public void requestStop(){

    stopRequested = true;

    }

    }

    Рассмотрим идиому двойной проверки отложенной инициализации: 

    /*

    * Двойная проверка отложенной инициализации - неправильная

    */

    private static Foo foo = null;

    public staic Foo getFoo(){

    if (foo = null) {

    synchronized(Foo.class) {

    if (foo = null)

    foo = new Foo();

    }

    }

    return foo;

    }

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

    Решить проблему можно несколькими способами:

    1. Нормальная статическая (не отложенная) инициализация

      /*

      * Нормальная статическая (не отложенная) инициализация

      */

      private static final Foo foo = new Foo();

      public staic Foo getFoo(){

      return foo;

      }

    2. Правильно синхронизированная отложенная инициализация

      /*

      * Правильно синхронизированная отложенная инициализация

      */

      private static Foo foo = null;

      public staic synchronized Foo getFoo(){

      if (foo = null) {

      foo = new Foo();

      }

      return foo;

      }

    3. Идиома класса, выполняющего инициализацию по запросу

    /*

    * Идиома класса, выполняющего инициализацию по запросу

    */

    private static class FooHolder {

    private static final Foo foo = new Foo();

    }

     

    public staic Foo getFoo(){ return FooHolder.foo;

    }

     

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

    7.2.11Избыточная синхронизация

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

    Рассмотрим класс, в котором реализована очередь заданий

    /*

    * Очередь заданий - неправильная

    */

     

    import java.util.*;

     

    public abstract class WorkQueue {

    private final List queue = new LinkedList();

     

    private boolean stopped = false;

     

    protected WorkQueue() {

    new WorkerThread().start();

    }

     

    public final void enqueue(Object workItem) {

    synchronized (queue) {

    queue.add(workItem);

    queue.notify();

    }

    }

     

    public final void stop() {

    synchronized (queue) {

    stopped = true;

    queue.notify();

    }

    }

     

    protected abstract void processItem(Object workItem)

    throws InterruptedException;

     

    //Вызов чужого метода из синхронизированного блока!

    private class WorkerThread extends Thread {

    public void run() {

    while (true) {

    synchronized (queue) {

    try {

    while (queue.isEmpty() && !stopped)

    queue.wait();

    } catch (InterruptedException e) {

    return;

    }

    if (stopped)

    return;

     

    Object workItem = queue.remove(0);

    try {

    processItem(workItem);

    // Блокировка

    } catch (InterruptedException e) {

    return;

    }

     

    }

    }

    }

    }

     

    }

     

     

    class DisplayQueue extends WorkQueue{

    protected void processItem(Object workItem) throws InterruptedException {

    System.out.println(workItem);

    Thread.sleep(1000);

    }

    }

    class DeadlockQueue extends WorkQueue{

    protected void processItem(final Object workItem) throws InterruptedException {

    //создаем новый поток, который возвращает workItem в очередь

    Thread child = new Thread(){

    public void run() { enqueue(workItem); }};

    child.start();

    child.join(); //взаимная блокировка!!!

    }

    }

    Эта проблема легко устранима, достаточно вынести вызов метода processItem() за пределы синхронизированного блока.

    // Чужой метод за пределами синхронизированного блока –

    // ”открытый вызов”

    private class WorkerThread extends Thread {

    public void run() {

    while (true) {

    Object workItem = null;

    synchronized (queue) {

    try {

    while (queue.isEmpty() && !stopped)

    queue.wait();

    } catch (InterruptedException e) {

    return;

    }

    if (stopped)

    return;

     

    workItem = queue.remove(0);

    }

    try {

    processItem(workItem);

    // Блокировки нет

    } catch (InterruptedException e) {

    return;

    }

    }

    }

    }

    }

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

    1. заблокировать ресурс;

    2. проверить совместно используемые данные;

    3. преобразовать данные при необходимости;

    4. разблокировать ресурс.

    7.2.12Вызов метода wait

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

    synchronized (obj) {

    try {

    while (< условие не выполнено >)

    obj.wait();

    } catch (InterruptedException e) {

    //TODO обработка исключения

    }

    //Выполнение действия, соответствующего условию

    }

    Цикл нужен для проверки соответствующего условия до и после ожидания.

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

    Проверка условия по завершении ожидания и вновь ожидание, если условие не выполнено, необходимы для обеспечения безопасности. Если поток производит операцию, когда условие не выполнено, он может нарушить инварианты, защищенные блокировкой. Существует несколько причин, по которым поток может ”проснуться” при невыполненном условии:

    • За время от момента, когда поток вызывает метод notify, и до того момента, когда ожидающий поток проснется, другой поток может успеть заблокировать объект и поменять его защищенное состояние.

    • Другой поток может случайно или умышленно вызвать notify, когда условие еще не выполнено.

    • Во время ”побудки” потоков извещающий поток может вызывать notifyAll, даже если условие пробуждения выполнено лишь для некоторых ожидающих потоков.

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

    7.2.13Документирование уровней безопасности

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

    • Неизменяемый (immutable). Экземпляры такого класса выглядят для своих клиентов как константы. Никакой внешней синхронизации не требуется. Примером являются String, Integer и BigInteger.

    Неизменяемый класс – это класс, экземпляры которого нельзя поменять. Вся информация, содержащаяся в любом его экземпляре, записывается в момент его создания и остается неизменной в течение всего времени существования этого объекта. В библиотеке платформы Java имеется целый ряд неизменяемых классов, в том числе String, простые классы-оболочки, BigInteger и BigDecimal. Неизменяемые классы легче разрабатывать и использовать, они менее подвержены ошибкам и более надежны. Единственный настоящий недостаток неизменяемых классов заключается в том, что для каждого уникального значения им нужен отдельный объект.

    • С поддержкой многопоточности (thread-safe). Экземпляры такого класса могут изменяться, однако все методы имеют довольно надежную внутреннюю синхронизацию, чтобы эти экземпляры могли параллельно использовать несколько потоков безо всякой внешней синхронизации. Примеры: Random и java.util.Timer.

    • С условной поддержкой многопоточности (conditionally thread-safe). То же, что и с поддержкой многопоточности, за исключением того, что класс содержит методы, которые должны вызываться последовательно один за другим без взаимного влияния со стороны других потоков. Для исключения такого влияния клиент должен установить соответствующую блокировку на время выполнения этой последовательности. Примеры: HashTable и Vector, чьи итераторы требуют внешней синхронизации.

    • Совместимый с многопоточностью (thread-compatible). Экземпляры такого класса можно безопасно использовать при работе с параллельными потоками, если каждый вызов метода окружить внешней синхронизацией. Примером являются реализации коллекций общего назначения, такие как ArrayList и HashMap.

    • Несовместимый с многопоточностью (thread-hostile). Этот класс небезопасен при параллельной работе с несколькими потоками, даже если вызовы всех методов окружены внешней синхронизацией. Обычно несовместимость связана с тем обстоятельством, что эти методы меняют некие статические данные, которые оказывают влияние на другие потоки. Пример, метод System.runFinalizerOnExit несовместим с многопоточностью и признан устаревшим.

    Документированию классов с условной поддержкой многопоточности необходимо уделять особое внимание.

    7.2.14Работа с графикой Графика 2d

    Начиная с версии JDK 1.2 компания Sun включила в состав пакета разработчика более мощный графический пакет java.awt.Graphics2D. Это достаточно полный и гибкий набор инструментов для графического вывода, поддерживающий дизайн различных линий, текста, картинок, имеющий более богатый и полнофункциональный интерфейс для пользователя.

    В состав API пакета входит:

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

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

    • Механизм для выявления отклонений (дефектов) графических форм, текста и картинок.

    • Механизм управления перекрывающимися объектами.

    • Улучшенная цветовая поддержка, облегчающая управление цветом.

    • Поддержка печати сложных документов.

    Основной механизм рисования в java.awt.Graphics2D такой же, как в предыдущих версиях JDK - графическая система управляет тем, когда и что программа должна рисовать. Когда некоторый компонент должен отобразить себя на экране, его метод paint() или update() автоматически вызывается с соответствующим ему контекстом Graphics. В новой версии JDK в API введен новый тип графического контекста - класс Graphics2D, который расширяет класс Graphics, обеспечивая улучшенную графику и свойства рисования.

    Использование графики 2D в методах paint() осуществляется теперь следующим образом:

    public void paint (Graphics g) { Graphics2D g2 = (Graphics2D) g; ... }

    Пространства координат

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

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

    В связи с этим обстоятельством в Java 2D поддерживаются два координатных пространства:

    1. Пространство пользователя - пространство, в котором определены все графические примитивы.

    2. Пространство устройства - координатная система выходного устройства, такого как экран, окно или принтер.

    3. Пространство пользователя - это независимая от устройства локальная система координат, используемая программой пользователя. Все геометрические объекты, рисуемые методами Java 2D, определены именно в пространстве пользователя.

    Если используется преобразование координат, пространство пользователя - пространство устройства, заданное по умолчанию, то начало координат принимается находящимся в верхнем левом углу области рисования. Координата x возрастает слева направо, а координата y - сверху вниз.

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

    Формы

    Следующие классы пакета java.awt.geom определяют общие графические примитивы (формы), такие как точки, прямые и кривые линии, дуги, прямоугольники и эллипсы:

    1. Arc2D

    2. Ellipse2D

    3. QuadCurve2D

    4. Area

    5. GeneralPath

    6. Rectangle2D

    7. CubicCurve2D

    8. Line2D

    9. RectangularShape

    10. Dimension2D

    11. Point2D

    12. RoundRectangle2D

    Кроме классов Point2D и Dimension2D, каждый из этих классов (геометрических форм) наследует интерфейс Shape, в котором объявлена система методов для описания и инспектирования 2D геометрических объектов.

    С помощью этих классов пользователь может создавать практически любую геометрическую форму и рисовать ее посредством вызова методов draw() или fill() контекста Graphics2D. Ниже показано приложение, использующее описанные формы.

    Режим рисования

    Класс Graphics обеспечивает два различных режима рисования: режим раскраски и режим XOR.

    Режим рисования относится к способу, в соответствии с которым новые нарисованные линии или формы взаимодействуют с тем, что уже было нарисовано. В режиме раскраски новая форма стирает то, что было нарисовано раньше. Пиксели, которые образуют форму, окрашиваются в цвета, заданные новой формой.

    В режиме XOR этого не происходит. Форма рисуется в цвет, который зависит от цвета формы, которую нужно нарисовать и цвета уже существующей формы.

    Создание цвета

    Цвет, как и все в Java, – объект определенного класса, а именно, класса color. Основу класса составляют семь конструкторов цвета. Самый простой конструктор: Color(int red, int green, int blue) - создает цвет, получающийся как смесь красной red, зеленой green и синей blue составляющих. Эта цветовая модель называется RGB. Каждая составляющая меняется от 0 (отсутствие оставляющей) до 255 (полная интенсивность этой составляющей). Например:

    Color pureRed = new Color(255, 0, 0); 

    Color pureGreen = new Color(0, 255, 0);

    определяют чистый ярко-красный pureRed и чистый ярко-зеленый pureGreen цвета.

    Во втором конструкторе интенсивность составляющих можно изменять более гладко вещественными числами от 0.0 (отсутствие составляющей) до 1.0 (полная интенсивность составляющей):Color(float red, float green, float blue) 

    Например:

    Color someColor = new Color(O.OSf, 0.4f, 0.95f);

    Третий конструктор Color(int rgb) задает все три составляющие в одном целом числе. В битах 16—23 записывается красная составляющая, в битах 8—15 — зеленая, а в битах 0—7 — синяя составляющая цвета. Например:

    Color с = new Color(OXFF8F48FF);

    Здесь красная составляющая задана с интенсивностью 0x8F, зеленая — 0x48, синяя — 0xFF.

    Следующие три конструктора:

    Color(int red, int green, int blue, int alpha)

    Color(float red, float green, float blue, float alpha)

    Color(int rgb, boolean hasAlpha)

    вводят четвертую составляющую цвета, так называемую "альфу", определяющую прозрачность цвета. Эта составляющая проявляет себя при наложении одного цвета на другой. Если альфа равна 255 или 1,0, то цвет совершенно непрозрачен, предыдущий цвет не просвечивает сквозь него. Если альфа равна 0 или 0,0, то цвет абсолютно прозрачен, для каждого пиксела виден только предыдущий цвет.

    Последний из этих конструкторов учитывает составляющую альфа, находящуюся в битах 24—31, если параметр hasAipha равен true. Если же hasAipha равно false, то составляющая альфа считается равной 255, независимо от того, что записано в старших битах параметра rgb.

    Первые три конструктора создают непрозрачный цвет с альфой, равной 255 или 1,0.

    Седьмой конструктор: Color(ColorSpace cspace, float[] components, float alpha) позволяет создавать цвет не только в цветовой модели (color model) RGB, но и в других моделях: CMYK, HSB, CIEXYZ, определенных объектом класса

    ColorSpace.

    Для создания цвета в модели HSB можно воспользоваться статическим методом getHSBColor(float hue, float saturation, float brightness).

    Если нет необходимости тщательно подбирать цвета, то можно просто воспользоваться одной из тринадцати статических констант типа color, имеющихся в классе color. Вопреки соглашению "Code Conventions" они записываются строчными буквами: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow.

    Методы класса Color позволяют получить составляющие текущего цвета: getRedO, getGreenO, getBlue(), getAlphaO, getRGBO, getColorSpace(), getComponents ().

    Два метода создают более яркий brighter!) и более темный darker о цвета по сравнению с текущим цветом. Они полезны, если надо выделить активный компонент или, наоборот, показать неактивный компонент бледнее остальных компонентов.

    Два статических метода возвращают цвет, преобразованный из цветовой модели RGB в HSB и обратно: 

    float[] RGBtoHSB(int red, int green, int blue, float[] hsb) 

    int HSBtoRGB(int hue, int saturation, int brightness)

    Создав цвет, можно рисовать им в графическом контексте.

    Основные методы рисования

    Основной метод рисования – drawLine(int xl, int yl, int х2, int y2) вычерчивает текущим цветом отрезок прямой между точками с координатами (xl, yl) и (х2, у2).

    Одного этого метода достаточно, чтобы, нарисовать любую картину по точкам, вычерчивая каждую точку с координатами (х, у) методом drawLine (x, у, х, у) и меняя цвета от точки к точке.

    Другие графические примитивы:

    • drawRect(int x, int у, int width, int height) – чертит прямоугольник со сторонами, параллельными краям экрана, задаваемый координатами верхнего левого угла (х, у), шириной width пикселов и высотой height пикселов;

    • draw3DRect(int x, int у, int width, int height, boolean raised) – чертит прямоугольник, как будто выделяющийся из плоскости рисования, если аргумент raised равен true, или как будто вдавленный в плоскость, если аргумент raised равен false;

    • drawOval(int x, int у, int width, int height) – чертит овал, вписанный в прямоугольник, заданный аргументами метода. Если width == height, то получится окружность;

    • drawArc(int x, int у, int width, int height, int startAngle, int arc) – чертит дугу овала, вписанного в прямоугольник, заданный первыми четырьмя аргументами. Дуга имеет величину arc градусов и отсчитывается от угла startAngle. Угол отсчитывается в градусах от оси Ох. Положительный угол отсчитывается против часовой стрелки, отрицательный — по часовой стрелке;

    • drawRoundRect (int x, int у, int width, int height, int arcWidth, int arcHeight) – чертит прямоугольник с закругленными краями. Закругления вычерчиваются четвертинками овалов, вписанных в прямоугольники шириной arcwidth и высотой arcHeight, построенные в углах основного прямоугольника;

    • drawPolyline(int[] xPoints, int[] yPoints, int nPoints) – чертит ломаную с вершинами в точках <xPoints[i], ypoints[i]) и числом вершин nPoints;

    • drawPolygon(int[] xPoints, int[] yPoints, int nPoints) – чертит замкнутую ломаную, проводя замыкающий отрезок прямой между первой и последней точкой;

    • drawFoiygon(Polygon p) – чертит замкнутую ломаную, вершины которой заданы объектом р класса Polygon.

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

    • Polygon() – создает пустой объект;

    • Polygon(int[] xPoints, int[] yPoints, int nPoints) – задаются вершины многоугольника (xPoints[i], yPoints[i]) и их число nPoints

    После создания объекта в него можно добавлять вершины методом addPoint(int x, int у)

    Логические методы contains () позволяют проверить, не лежит ли в многоугольнике заданная аргументами метода точка, отрезок прямой или целый прямоугольник со сторонами, параллельными сторонам экрана.

    Логические методы intersects () позволяют проверить, не пересекается ли с данным многоугольником отрезок прямой, заданный аргументами метода, или прямоугольник со сторонами, параллельными сторонам экрана.

    Методы getBounds() и getBounds2D() возвращают прямоугольник, целиком содержащий в себе данный многоугольник.

    Вернемся к методам класса Graphics. Несколько методов вычерчивают фигуры, залитые текущим цветом: fillRect(), fill3DRect(), fillArco, filloval(), fillPoiygon(), fillRoundRect(). У них такие же аргументы, как и у соответствующих методов, вычерчивающих незаполненные фигуры.

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

    public void paint(Graphics g)(

      Color initColor = g.getColor();   

    // Сохраняем исходный цвет

      g.setColor(new Color(0, 0, 255)); 

    // Устанавливаем цвет фона

                                        

    // Заливаем область рисования

      g.fillRect(0, 0, getSizeO.width - 1, getSize().height - 1);'

      g.setColor(initColor);            

    // Восстанавливаем исходный цвет

    // Дальнейшие действия

    }

    В классе Graphics собраны только самые необходимые средства рисования. Нет даже метода, задающего цвет фона (хотя можно задать цвет фона компонента методом setBackground() класса component). Средства рисования, вывода текста в область рисования и вывода изображений значительно дополнены и расширены в подклассе Graphics2D, входящем в систему Java 2D. Например, в нем есть метод задания цвета фона setBackground(Color с).

    Для вывода текста в область рисования текущим цветом и шрифтом, начиная с точки (х, у), в, классе Graphics есть несколько методов:

    • drawstring (String s, int x, int y) – выводит строку s;

    • drawBytes(byte[] b, int offset, int length, int x, int у) – выводит length элементов массива байтов ь, начиная с индекса offset;

    • drawChars(chart] ch, int offset, int length, int x, int у) – выводит length элементов массива символов ch, начиная с индекса offset.

    Четвертый метод выводит текст, занесенный в объект класса, реализующего интерфейс AttributedCharacteriterator. Это позволяет задавать свой шрифт для каждого выводимого симвбла: drawstring(AttributedCharacteriterator iter, int x, int y) .

    Точка (х, у) — это левая нижняя точка первой буквы текста на базовой линии (baseline) вывода шрифта.

    Java2D

    В систему пакетов и классов Java 2D, основа которой— класс Graphics2D пакета java.awt, внесено несколько принципиально новых положений.

    Кроме координатной системы, принятой в классе Graphics и названной координатным пространством пользователя (User Space), введена еще система координат устройства вывода (Device Space): экрана монитора, принтера. Методы класса Graphics2D автоматически переводят (transform) систему координат пользователя в систему координат устройства при выводе графики. Преобразование координат пользователя в координаты устройства можно задать "вручную", причем преобразованием способно служить любое аффинное преобразование плоскости, в частности, поворот на любой угол и/или сжатие/растяжение. Оно определяется как объект класса AffineTransform. Его можно установить как преобразование по умолчанию методом setTransform(). Возможно выполнять преобразование "на лету" методами transform о и translate и делать композицию преобразований методом concatenate().

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

    Графические примитивы: прямоугольник, овал, дуга и др., реализуют теперь новый интерфейс shape пакета java.awt. Для их вычерчивания можно использовать новый единый для всех фигур метод drawo, аргументом которого способен служить любой объект, реализовавший интерфейс shape. Введен метод fill о, заполняющий фигуры— объекты класса, реализовавшего интерфейс shape.

    Для вычерчивания (stroke) линий введено понятие пера (реп). Свойства пера описывает интерфейс stroke. Класс Basicstroke реализует этот интерфейс. Перо обладает четырьмя характеристиками:

    • оно имеет толщину (width) в один (по умолчанию) или несколько пикселов;

    • оно может закончить линию (end cap) закруглением — статическая константа CAP_ROUND, прямым обрезом — CAP_SQUARE (по умолчанию), или не фиксировать определенный способ окончания — CAP_BUTT;

    • оно может сопрягать линии (line joins) закруглением — статическая константа JOIN_ROOND, отрезком прямой — JOIN_BEVEL, или просто состыковывать — JOIN_MITER (по умолчанию);

    • оно может чертить линию различными пунктирами (dash) и штрих-пунктирами, длины штрихов и промежутков задаются в массиве, элементы массива с четными индексами задают длину штриха, с нечетными индексами — длину промежутка между штрихами.

    Методы заполнения фигур описаны в интерфейсе Paint. Три класса реализуют этот интерфейс. Класс color реализует его сплошной (solid) заливкой, класс GradientPaint — градиентным (gradient) заполнением, при котором цвет плавно меняется от одной заданной точки к другой заданной точке, класс Texturepaint — заполнением по предварительно заданному образцу (pattern fill).

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

    Кроме имени, стиля и размера, шрифт получил много дополнительных атрибутов, например, преобразование координат, подчеркивание или перечеркивание текста, вывод текста справа налево. Цвет текста и его фона являются теперь атрибутами самого текста, а не графического контекста. Можно задать разную ширину символов шрифта, надстрочные и подстрочные индексы. Атрибуты устанавливаются константами класса TextAttribute.

    Процесс визуализации (rendering) регулируется правилами (hints), определенными КОНСТантами класса RenderingHints.

    С такими возможностями Java 2D стала полноценной системой рисования, вывода текста и изображений. Посмотрим, как реализованы эти возможности, и как ими можно воспользоваться.

    Рисование фигур средствами Java2d

    Характеристики пера для рисования фигур описаны в интерфейсе stroke. В Java 2D есть пока только один класс, реализующий этот интерфейс — класс BasicStroke.

    Класс BasicStroke

    Конструкторы класса BasicStroke определяют характеристики пера. Основной конструктор BasicStroke(float width, int cap, int join, float miter, float[] dash, float dashBegin) задает:

    1. толщину пера width в пикселах;

    2. оформление конца линии cap; это одна из констант:

      1. CAP_ROUND — закругленный конец линии;

      2. CAP_SQUARE — квадратный конец линии;

      3. CAP_BUTT — оформление отсутствует;

    3. способ сопряжения линий join; это одна из констант:

      1. JOIN_ROUND — линии сопрягаются дугой окружности;

      2. JOIN_BEVEL — линии сопрягаются отрезком прямой, перпендикуляр-ным биссектрисе угла между линиями;

      3. JOIN_MITER — линии просто стыкуются;

    4. расстояние между линиями miter, начиная с которого применяется сопряжение JOIN_MITER;

    5. длину штрихов и промежутков между штрихами — массив dash; элементы массива с четными индексами задают длину штриха в пикселах, элементы с нечетными индексами — длину промежутка; массив перебирается циклически;

    6. индекс dashBegin, начиная с которого перебираются элементы массива dash.

    Остальные конструкторы задают некоторые характеристики по умолчанию:

    1. BasicStroke (float width, int cap, int join, float miter) — сплошная линия;

    2. BasicStroke (float width, int cap, int join) — сплошная линия с сопряжением JOIN_ROUND или JOIN_BEVEL; для сопряжения JOIN_MITER задается значение miter = 10.0f;

    3. BasicStroke (float width) — прямой обрез CAP_SQUARE и сопряжение JOIN_MITER со значением miter = 10.0f; 

    4. BasicStroke () — ширина 1.0f.

    После создания пера одним из конструкторов и установки пера методом setStroke о можно рисовать различные фигуры методами draw() и fill(). Общие свойства фигур, которые можно нарисовать методом draw о класса Graphics2D, описаны в интерфейсе shape. Этот интерфейс реализован для создания обычного набора фигур — прямоугольников, прямых, эллипсов, дуг , точек — классами Rectangle2D, RoundRectangle2D, Line2D, Ellipse2D, Arc2D, Point2D пакета java.awt.geom. В этом пакете есть еще классы Cubiccurve2D и QuadCurve2D для создания кривых третьего и второго порядка.

    Все эти классы абстрактные, но существуют их реализации — вложенные классы Double и Float для задания координат числами соответствующего типа. В Листинге 9.4 использованы классы Rectangle2D.Double И Line2d.Double для вычерчивания прямоугольников и отрезков.

    В пакете java.awt.geom есть еще один интересный класс — GeneralPath. Объекты этого класса могут содержать сложные конструкции, составленные из отрезков прямых или кривых линий и прочих фигур, соединенных или не соединенных между собой. Более того, поскольку этот класс реализует интерфейс shape, его экземпляры сами являются фигурами и могут быть элементами других объектов класса GeneralPath.

    Класс GeneralPath

    Вначале создается пустой объект класса GeneralPath конструктором по умолчанию GeneralPath() или объект, содержащий одну фигуру, конструктором GeneralPath (Shape sh). Затем к этому объекту добавляются фигуры методом append(Shape sh, boolean connect)

    Если параметр connect равен true, то новая фигура соединяется с предыдущими фигурами с помощью текущего пера. В объекте есть текущая точка. Вначале ее координаты (0, 0), затем ее можно переместить в точку (х, у) методом moveTo (float x, float у). От текущей точки к точке (х, у) можно провести: 

    • отрезок прямой методом lineTo(float x, float у);

    • отрезок квадратичной кривой методом quadTot float xi, float yl, float x, float y),

    • кривую Безье методом curveTo(float xl, float yl, float x2, float y2, float x, float y).

    Текущей точкой после этого становится точка (х, у). Начальную и конечную точки можно соединить методом ciosePath(). Вот как можно создать треугольник с заданными вершинами:

    GeneralPath p = new GeneralPath();

    p.moveTo(xl, yl); // Переносим текущую точку в первую вершину,

    p.lineTo(x2, y2); // проводим сторону треугольника до второй вершины,

    p.lineTo(x3, уЗ); // проводим вторую сторону,

    p.closePathf);    // проводим третью сторону до первой вершины

    Способы заполнения фигур определены в интерфейсе Paint. В настоящее время Java 2D содержит три реализации этого интерфейса — классы color, GradientPaint и TexturePamt. Класс Color нам известен, посмотрим, какие способы заливки предлагают классы GradientPaint И TexturePaint.

    Классы GradientPaint и TexturePaint

    Класс GradientPaint предлагает сделать заливку следующим образом.

    В двух точках M и N устанавливаются разные цвета. В точке M(xi, yi) задается цвет cl, в точке N(х2, у2) — цвет с2. Цвет заливки гладко меняется от cl к с2 вдоль прямой, соединяющей точки M и N, оставаясь постоянным вдоль каждой прямой, перпендикулярной прямой мы. Такую заливку создает конструктор GradientPaint(float xl, float yl, Color cl, float x2, float y2, Color c2)

    Второй конструктор GradientPaint(float xl, float yl, Color cl, float x2, float y2, Color c2, boolean cyclic).

    Еще два конструктора задают точки как объекты класса Point2D. Класс TexturePaint поступает сложнее. Сначала создается буфер — объект класса Bufferedlmage из пакета java.awt. image. Это большой сложный класс. Мы с ним еще встретимся в главе 15, а пока нам понадобится только его графический контекст, управляемый экземпляром класса Graphics2D. Этот экземпляр можно получить методом createGraphics () класса Bufferedlmage. Графический контекст буфера заполняется фигурой, которая будет служить образцом заполнения.

    Затем по буферу создается объект класса TexturePaint. При этом еще задается прямоугольник, размеры которого будут размерами образца заполнения. Конструктор выглядит так: TexturePaint(Bufferedlmage buffer, Rectangle2D anchor).

    7.3 Порядок выполнения работы

    1. Ознакомиться с заданием на лабораторную работу

    2. Спроектировать три вычислительных потока.

    3. Написать тестовую программу, демонстрирующую работоспособность вычислительных потоков. Тестовая программа должна:

  • Содержать минимум три вычислительных потока;

  • Синхронизация потоков при доступе к общим ресурсам должна осуществляться с использованием методов wait() и notify();

  • Управление потоками из основного потока: запуск, останов;

  • При завершении основного потока необходимо выполнить корректную остановку двух вычислительных потоков. Использовать технологию, описанную в процедуре остановки потока пункта 7.2.1 и обязательно использовать метод join().

    1. Составить отчет о выполнении лабораторной работы.

    7.4 Содержимое отчета

      1. Название и тема лабораторной работы;

      2. Цель лабораторной работы;

      3. Краткие теоретические сведения;

      4. Ход выполнения работы (реальный, а не переписанный из раздела 7.3);

      5. Исходные тексты программ;

      6. Диаграмма классов;

      7. Выводы.

    7.5 Контрольные вопросы

    1. Потоки в Java;

    2. Управление синхронизацией потоков, методов;

    3. Назначения обобщений;

    4. Основные классы для реализации потоков в Java;

    5. Многопоточность;

    6. Процессы и приоритеты;

    7. Перечислите основные методы рисования;

    8. Объясните понятие избыточной синхронизации;

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

    10. Каким образом возможно задать цвет в Java. Какая цветовая модель при этом используется.

    РекомендУемая литература

    1. Буч Г. Объектно-ориентированное программирование с примерами применения: Пер. с англ. – М.:Кокорд, 1992. -519с

    2. Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений на С++: Пер. с англ. – М.:Диалект, 1999

    3. Страуструп Б. Язык программирования С++: Пер. с англ. – М.:Новый диалект, 1999. -519с

    4. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. – СПб.:Питер, 2001

    5. Эккель Б. Философия Java. Библиотека программиста. – СПб.: Питер, 2003

    6. Блох Д. Java. Эффективное программирование. – Лори (Sun publiched)

  • Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]