Методичка java. Методичка По Шаблонам Java. Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия
Скачать 1.94 Mb.
|
3. ПРАКТИЧЕСКАЯ РАБОТА № 3 Цель работы Знакомство с конкурентным программированием в Java. Потокобезопасность, ключевое слово syncrhonized, мьютексы, семафоры, мониторы, барьеры. 3.1. Теоретическая часть В Java для реализации многопоточности используются нативные потоки (но в скором времени может появиться Project Loom, который предоставит возможность использовать «зеленые» потоки в Java). Для работы с потоком используется класс Thread. Но часто может потребоваться, чтобы разные потоки обращались к одним и тем же данным, и это может привести к отсутствию консистентности данных, так как фактически ни одна команда не является идеально атомарной. Даже инкремент целочисленной переменной внутри выполняется не как одна команда, и, если несколько потоков будут инкрементировать одну переменную, будут возникать странные результаты. static volatile int buf; static void increment() { buf++; } public static void main(String[] args) throws Exception { buf = 0; Thread one = new Thread(()->{ for (int i = 0; i < 5000; i++) { increment(); } }); Thread two = new Thread(()->{ for (int i = 0; i < 5000; i++) { increment(); } }); one.start(); two.start(); Thread.sleep(3000); System.out.println(buf); } Каждый раз будет выводиться разное значение переменной buf, и причина будет не в том, что Thread не успевает доработать (попробуйте увеличить значение sleep). Проблема будет в том, что инкремент не атомарный, и при изменении непосредственно значения в памяти оно уже могло измениться, и эти изменения просто пропадут. Поэтому требуется как- то добиться атомарности метода increment. Потокобезопасность Для потокобезопасности существует такое понятие, как мьютекс. Мьютекс – специальный объект для синхронизации потоков. У каждого объекта и класса существует мьютекс. Управлять мьютексом напрямую невозможно, им полностью управляет Java Virtual Machine (JVM). Монитор – надстройка над мьютексом, позволяющая обеспечить синхронизацию. Для работы с монитором используется несколько технологий. Ключевое слово synchronized При использовании слова synchonized происходит захват монитором определенного объекта. Попробуем добавить ключевое слово synchonized: synchronized static void increment() { buf++; } И при запуске программы она возвращает нужное нам число – 10000. Что же произошло? При входе в метод increment активируется блок на класс, поэтому другой поток не может войти в данный метод и ждет завершения предыдущего. Слово synchronized можно применять к определенному методу (тогда блокировка производится на класс или объект, чей метод вызывается) или на определенный объект (например, synchronized(this)). Имплементация класса Lock Добавим новое статическое поле Lock: private static final Lock lock = new ReentrantLock(); И изменим код метода increment: lock.lock(); buf++; lock.unlock(); При запуске программы она покажет 10000. Блокировка работает. Использование Lock может понадобиться для более точечной блокировки. Также можно использовать отдельно ReadLock и WriteLock для того, чтобы не блокировать Thread чтения, если нет записи. Использование Semaphore Semaphore – класс, который принимает количество возможных разрешений. Когда количество разрешений заканчивается, следующий Thread, пытающийся его получить, блокируется. private static final Semaphore semaphore = new Semaphore(1); static void increment() { try { semaphore.acquire(); buf++; semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } А если мы хотим воспользоваться коллекцией? Нам нужно вручную делать ее потокобезопасной? Нет, в Java есть даже 2 типа потокобезопасных коллекций: – synchonized коллекции (Collections.synchronizedList()); – конкурентные коллекции (ConcurrentHashMap). Они имеют свои плюсы и минусы, и для каждой ситуации нужно выбирать подходящую коллекцию. 3.2. Задание Создать свои потокобезопасные имплементации интерфейсов в соответствии с вариантом индивидуального задания. 3.3. Варианты индивидуального задания 1) Map с использованием ключевого слова synchronized, List с использованием Semaphore. 2) Map с использованием Semaphore, List с использованием Lock. 3) Map с использованием Lock, Set с использованием ключевого слова synchronized. 4) Set с использованием ключевого слова synchronized, Map с использованием Lock. 5) Set с использованием Semaphore, List с использованием ключевого слова synchronized. 6) Set с использованием Lock, Map с использованием Semaphore. 7) List с использованием ключевого слова synchronized, Set с использованием Semaphore. 8) List с использованием Semaphore, Map с использованием ключевого слова synchronized. 9) List с использованием Lock, Map с использованием Semaphore. 10) Map с использованием ключевого слова synchronized, Set с использованием Semaphore. 11) Set с использованием ключевого слова synchronized, List с использованием Lock. 12) Map с использованием Semaphore, Set с использованием ключевого слова synchronized. 13) Set с использованием Semaphore, List с использованием Lock. 14) List с использованием Semaphore, Set с использованием ключевого слова synchronized. 15) Map с использованием Lock, Set с использованием Semaphore. 4. ПРАКТИЧЕСКАЯ РАБОТА № 4 Цель работы Работа с ExecutorService, CompletableFuture. 4.1. Теоретическая часть Работать с потоками напрямую является достаточно неудобным занятием. Мы все любим удобные абстракции, и ExecutorsService с CompletableFuture являются прекрасными абстракциями, которыми можно не бояться пользоваться. Также для реализации многопоточного программирования часто используется асинхронность в том или ином виде. Асинхронность – возможность выполнения блока программы в неблокирующем виде системного вызова, что позволяет потоку программы продолжить обработку. Ниже перечислены некоторые реализации асинхронности. Использование callback-функций Когда нужно что-то выполнить асинхронно, дополнительно в параметр передается некая функция – callback, которая вызывается при завершении асинхронного блока, что позволяет выполнить некую логику по завершении асинхронного блока. У данной реализации есть один минус, он называется callback hell – усложнение читаемости кода. На рисунке 2 приведен пример использования callback-функций. Рисунок 2 – Пример использования callback-функций Async/await Async/await – асинхронные функции помечаются как async, что позволяет их выполнить параллельно другой логике. Если требуется получить что-то из асинхронной функции, нужно использовать await, но await можно применять только в асинхронной функции, что не даст выполнить асинхронную тяжелую функцию с блокированием. Минус – малая вариативность, почти ничего невозможно настроить, или, в случае чего, остановить асинхронный код. Корутины Корутины – чаще всего так называют использование облегченных зеленых потоков, которые не являются нативным потоком, а стек вызова хранят в памяти вместо стека. Соответственно не происходит переключения контекста, но при этом корутины могут быть чрезвычайно вариативны, настраиваемы и читаемы. Реактивность Реактивность – в некотором роде полный отказ от блокирующего кода. Под реактивным программированием фактически понимается целая парадигма, ориентированная на представление всей информации в приложении как потоки данных, а также на распространение изменений. Лучше всего для изучения данной темы посмотреть Реактивный Манифест (https://www.reactivemanifesto.org/). ExecutorService ExecutorService – абстракция, представляющая собой некое множество потоков, которым можно передавать определенные задачи на выполнение. Данные задачи могут быть имплементацией интерфейсов Runnable и Callable. Возвращает Future для каждой задачи. Future является интерфейсом и представляет собой некое обещание, что по выполнению вернется некий объект. Также при помощи Future можно проверить, выполнилась ли задача, а также отменить ее. Примеры использования ExecutorService: ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("We run it"); }); executorService.submit(() -> System.out.println("Start")); Сначала выведется «We run it», а затем «Start». ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.submit(() -> { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("We run it"); }); executorService.submit(() -> System.out.println("Start")); Сначала выведется «Start», а затем «We run it». CompletableFuture CompletableFuture – иной способ для использования асинхронности, предоставляет удобный интерфейс для запуска асинхронных задач, например, runAsync. Позволяет легко строить цепочки из задач (Изучите код CompltetableFuture, и определите, создается ли для каждой задачи отдельный поток). Примеры кода с CompletableFuture: CompletableFuture CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return s + " world"; }); System.out.println(anotherFuture.get(3, TimeUnit.SECONDS)); 4.2. Задание Реализовать собственную имплементацию ExecutorService с единственным параметром конструктора – количеством потоков. 5. ПРАКТИЧЕСКАЯ РАБОТА № 5 Цель работы Познакомиться с паттернами проектирования, их определением и классификацией. Обзор паттернов GoF. Паттерн Синглтон. 5.1. Теоретическая часть Паттерны проектирования В процессе разработки мы сталкиваемся часто с достаточно похожими проблемами, встречаемся с тяжело поддерживаемым кодом. И хотелось бы иметь какие-то общепринятые способы решения для стандартных проблем. И такими решениями являются паттерны проектирования. Следует уточнить несколько моментов: 1) паттерн проектирования не решение всех проблем. Он лишь позволяет помочь решить какие-то конкретные случаи; 2) можно создавать хороший поддерживаемый код и без паттернов проектирования. Они лишь подспорье к разработке; 3) стоит знать паттерны проектирования, так как они используются во многих фреймворках и библиотеках, они облегчают дальнейшее понимание той или иной технологии. Паттернов огромное количество, но мы остановимся на важнейших – паттернах GoF. Паттерны GoF Паттерны GoF делятся на 3 вида: 1) порождающие; 2) структурные; 3) поведенческие. Паттерн Синглтон (Singleton) Синглтон – порождающий паттерн проектирования. Он позволяет гарантировать, что будет существовать ровно один объект существующего класса. Этот паттерн используется практически во всех возможных приложениях, является невероятно полезным. В первую очередь, чтобы реализовать данный паттерн, нужно запретить возможность другому коду вызывать конструктор. Для этого требуется приватный конструктор. Дальнейшая реализация различается у разных способов. Приведем пример двух способов: 1) через метод getInstance() с ленивой инициализацией: public class Singleton { private Singleton instance; public synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); return instance; } return instance; } } 2) через enum: public enum Singleton { INSTANCE; public Singleton getInstance() { return INSTANCE; } } 5.2. Задание Реализовать паттерн Singleton как минимум 3-мя способами. 6. ПРАКТИЧЕСКАЯ РАБОТА № 6 Цель работы Знакомство с реализацией порождающих паттернов проектирования. 6.1. Теоретическая часть Порождающие паттерны проектирования отвечают за удобное безопасное создание объектов или групп объектов. Паттерн «Фабричный метод» – определяет интерфейс создания объектов, позволяя подклассам менять тип создаваемых объектов (рисунок 3). Рисунок 3 – Паттерн «Фабричный метод» Паттерн «Абстрактная фабрика» – позволяет создавать семейства определенных объектов (рисунок 4). Фактически является расширением паттерна «Фабричный метод». Рисунок 4 – Паттерн «Абстрактная фабрика» Паттерн «Строитель» – разделяет создание объекта на отдельные шаги, а также позволяет использовать один и тот же код создания для получения различных представлений (рисунок 5). Рисунок 5 – Паттерн «Строитель» Паттерн «Прототип» – позволяет копировать объекты без обращения к приватному состоянию извне (рисунок 6). Рисунок 6 – Паттерн «Прототип» 6.2. Задание Написать реализацию паттернов «Фабричный метод», «Абстрактная фабрика», «Строитель», «Прототип». 7. ПРАКТИЧЕСКАЯ РАБОТА № 7 Цель работы Реализация структурных паттернов проектирования. 7.1. Теоретическая часть Структурные паттерны проектирования играют не менее важную роль, нежели остальные паттерны. Они отвечают за построение удобной структуры и иерархии классов, которая делает код более поддерживаемым. Рассмотрим основные структурные паттерны проектирования. Паттерн «Адаптер» позволяет какой-то объект с одним интерфейсом подстроить под другой интерфейс (рисунок 7). Рисунок 7 – Паттерн «Адаптер» Паттерн «Мост» разделяет класс на две независимые части – абстракцию и реализацию (рисунок 8). Рисунок 8 – Паттерн «Мост» Паттерн «Компоновщик» позволяет сгруппировать множество объектов в древовидную структуру (рисунок 9). Рисунок 9 – Паттерн «Компоновщик» Паттерн «Декоратор» позволяет добавлять новую функциональность объекту, является некоторой оберткой над классом (рисунок 10). Не управляет жизненным циклом объекта. Рисунок 10 – Паттерн «Декоратор» Паттерн «Фасад» используется для предоставления простой абстракции над некоей сложной системой (рисунок 11). Рисунок 11 – Паттерн «Фасад» Паттерн «Легковес» используется для экономии памяти, разделяя общее состояние между множеством объектов (рисунок 12). Удобно использовать, когда есть очень много объектов с схожим состоянием. Рисунок 12 – Паттерн «Легковес» Паттерн «Заместитель» (Прокси) подставляет вместо объектов специальные объекты заместители, добавляя дополнительную логику вокруг вызовов методов (рисунок 13). Может управлять жизненным циклом объекта, который проксирует. Рисунок 13 – Паттерн «Заместитель» 7.2. Задание Написать реализацию паттерна в соответствии с вариантом индивидуального задания. 7.3. Варианты индивидуального задания 1) Адаптер, Мост. 2) Мост, Компоновщик. 3) Компоновщик, Декоратор. 4) Декоратор, Фасад. 5) Фасад, Легковес. 6) Легковес, Заместитель. 7) Заместитель, Адаптер. 8) Адаптер, Декоратор. 9) Декоратор, Легковес. 10) Легковес, Компоновщик. 11) Компоновщик, Фасад. 12) Фасад, Заместитель. 13) Заместитель, Компоновщик. 14) Компоновщик, Фасад. 15) Фасад, Адаптер. 8. ПРАКТИЧЕСКАЯ РАБОТА № 8 Цель работы Реализация поведенческих паттернов проектирования. 8.1. Теоретическая часть Поведенческие паттерны проектирования позволяют расширять поведение системы и взаимодействие различных объектов между собой. Паттерн «Цепочка обязанностей» позволяет передавать запросы по специальной цепочке обработчиков (рисунок 14). Рисунок 14 – Паттерн «Цепочка обязанностей» Паттерн «Команда» инкапсулирует некий запрос в объект, позволяя передавать их другим объектам для обработки (рисунок 15). Рисунок 15 – Паттерн «Команда» Паттерн «Итератор» позволяет обходить множества элементов последовательно (рисунок 16). Рисунок 16 – Паттерн «Итератор» Паттерн «Посредник» перемещает взаимодействие между отдельными объектами в специальный класс-посредник (рисунок 17). Рисунок 17 – Паттерн «Посредник» Паттерн «Снимок» позволяет сохранять предыдущие состояние некоторого объекта, не раскрывая его реализации (рисунок 18). Рисунок 18 – Паттерн «Снимок» Паттерн «Наблюдатель» используется для создания механизма подписки на события (рисунок 19). Рисунок 19 – Паттерн «Наблюдатель» Паттерн «Состояние» позволяет объектам менять свое поведение в зависимости от состояния (рисунок 20). Рисунок 20 – Паттерн «Состояние» Паттерн «Стратегия» позволяет определить семейство различных алгоритмов, которые можно заменять (рисунок 21). Рисунок 21– Паттерн «Стратегия» Определяет некоторый алгоритм и позволяет его отдельные шаги делегировать подклассам (рисунок 22). Рисунок 22 – Паттерн «Стратегия» Паттерн «Посетитель» позволяет выполнять одну операцию над группой различных объектов, при этом позволяя создавать новую операцию без изменения классов, над которыми она выполняется (рисунок 23). Рисунок 23 – Паттерн «Посетитель» 8.2. Задание Написать реализацию паттерна в соответствии с вариантом индивидуального задания. 8.3. Варианты индивидуального задания 1) Цепочка обязанностей, Команда. 2) Команда, Итератор. 3) Итератор, Посредник. 4) Посредник, Снимок. 5) Снимок, Наблюдатель. 6) Наблюдатель, Состояние. 7) Состояние, Стратегия. 8) Стратегия, Шаблонный метод. 9) Шаблонный метод, Посетитель. 10) Посетитель, Команда. 11) Команда, Стратегия. 12) Посредник, Итератор. 13) Состояние, Цепочка обязанностей. 14) Шаблонный метод, Посетитель. 15) Посетитель, Посредник. |