Файл: Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 177
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Все действия должны производиться только с использованием Stream
API.
Индивидуальное задание должно быть оформлено в отдельном проекте.
Для проверки работоспособности выполненного индивидуального задания следует использовать отдельный класс с методом main.
2.3. Варианты индивидуального задания
1) Сортировка по имени, фильтрация по дате рождения большей, чем 24 июня 2000, сортировка по фамилии, нахождение суммы всех возрастов.
2) Сортировка по дате рождения, фильтрация по возрасту меньше, чем
50, сортировка по весу, конкатенация всех имен в одну большую строку через пробел.
3) Сортировка по весу в обратном порядке, фильтрация по фамилии не
Иванов, сортировка по возрасту, произведение всех возрастов.
4) Сортировка по второй букве имени, фильтрация по весу кратно 10, сортировка по произведению веса на возраст, произведение всех весов.
5) Сортировка по возрасту в обратном порядке, фильтрация по имени
«начинается с А», сортировка по дате рождения, расчет среднего веса.
6) Уменьшение веса каждого объекта на 5, фильтрация по дате рождения меньшей, чем 3 февраля 1999, конкатенация фамилий в строку через пробел.
7) Выбор первых 5 элементов списка, сортировка по дате рождения от старых к новым, фильтрация по весу меньше, чем 60, вывод имени и фамилии через пробел.
8) Фильтрация по возрасту больше чем 20, сортировка по последней букве имени, увеличение возраста каждого на 3, вычисление среднего возраста всех элементов.
9) Фильтрация по признаку «вес больше, чем возраст», сортировка по фамилии в обратном порядке, сумма всех весов.
10) Сортировка по второй букве имени в обратном порядке, фильтрация по весу больше, чем 60, сортировка по возрасту, произведение всех возрастов
11) Сортировка по имени в обратном порядке, фильтрация по возрасту больше, чем 20, выбор первых 3 элементов списка, конкатенация имен в строку через пробел.
12) Сортировка по последней букве фамилии, фильтрация по признаку
«возраст больше, чем вес», сортировка по дате рождения, произведение всех возрастов.
13) Сортировка по возрасту, фильтрация по возрасту меньше, чем 20, фильтрация по имени «содержит ‘е’», конкатенация первых букв имен.
14) Сортировка по сумме веса и возраста, фильтрация по весу кратно 5, выбор первых четырёх элементов, конкатенация имён через пробел.
15) Увеличение веса каждого объекта на 3, сортировка по весу в обратном порядке, фильтрация по дате рождения меньшей, чем 01.01.2000, сумма всех весов.
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 активируется блок на класс, поэтому другой поток не может войти в данный метод и ждет завершения предыдущего.
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 {