16 KiB
Техническое задание: многопоточный поиск индексов Student
Задача
Реализовать многопоточный поиск всех вхождений заданного объекта Student в MyList<Student>.
Метод не должен выводить никаких сообщений в консоль.
Результатом работы должен быть массив int[], содержащий индексы всех элементов исходной коллекции, которые равны переданному Student.
Сравнение выполнять через существующий Student.equals().
Входная информация
Основной метод принимает:
MyList<Student> students
Исходная коллекция студентов.
Student target
Студент, вхождения которого необходимо найти.
Выходная информация
Метод возвращает:
int[]
Массив должен содержать индексы всех совпавших элементов.
Пример:
Исходная коллекция:
[Student A, Student B, Student A, Student C, Student A]
Результат:
[0, 2, 4]
Индексы должны быть расположены в порядке возрастания.
При отсутствии совпадений:
[]
При пустой коллекции:
[]
Название класса
StudentOccurrenceIndexFinder
Публичные методы и контракты
findOccurrences
public int[] findOccurrences(
MyList<Student> students,
Student target
);
Метод должен:
- Проверить входные параметры.
- Разделить коллекцию на диапазоны индексов.
- Передать каждый диапазон отдельной задаче.
- Выполнить поиск в нескольких потоках.
- Получить от каждого потока найденные индексы.
- Объединить результаты.
- Вернуть индексы в порядке возрастания.
- Не изменять исходную коллекцию.
- Не изменять объекты
Student. - Не выводить ничего в консоль.
Вспомогательные методы
Количество и вид private методов могут быть выбраны разработчиком.
Рекомендуется выделить:
private int calculateThreadCount(int size);
Определяет количество рабочих потоков.
private int countChunkSize(int size, int threadCount);
Определяет размер диапазона для одной задачи.
private MyList<Integer> findInRange(
MyList<Student> students,
Student target,
int fromIndex,
int toIndex
);
Обрабатывает диапазон:
[fromIndex, toIndex)
и возвращает индексы найденных совпадений.
Работа с многопоточностью
Рекомендуемый подход
Не использовать общий контейнер результатов, к которому одновременно обращаются несколько потоков.
Также не рекомендуется использовать мьютексы (synchronized, Lock, ReentrantLock) для хранения результата.
Лучше использовать модель:
MyList<Student>
│
┌────────────┼────────────┐
↓ ↓ ↓
Поток 1 Поток 2 Поток 3
диапазон диапазон диапазон
│ │ │
↓ ↓ ↓
MyList<Integer> MyList<Integer> MyList<Integer>
└────────────┼────────────┘
↓
основной поток
↓
merge
↓
int[]
Каждый поток создаёт собственный контейнер MyList<Integer> и записывает найденные индексы только в него.
Это позволяет избежать состояния гонки без синхронизации общей структуры.
Разбиение коллекции на потоки
Количество потоков рекомендуется определять на основании:
Runtime.getRuntime().availableProcessors()
Количество потоков не должно превышать количество элементов:
threadCount = min(
availableProcessors(),
students.size()
)
Пример
Для коллекции из 10000 элементов и четырёх рабочих потоков:
Поток 1 → [0, 2500)
Поток 2 → [2500, 5000)
Поток 3 → [5000, 7500)
Поток 4 → [7500, 10000)
Каждый индекс должен принадлежать ровно одному диапазону.
Не допускаются:
- пропущенные индексы;
- обработка одного индекса несколькими потоками.
Поиск внутри потока
Каждая задача обрабатывает только свой диапазон.
Для каждого индекса:
Student student = students.get(index);
затем:
target.equals(student)
Если результат true, индекс добавляется в локальный:
MyList<Integer>
Пример:
Поток 1 → [0, 8, 15]
Поток 2 → [21, 24]
Поток 3 → [30, 42, 48]
Поток 4 → [63]
Объединение результатов
Основной поток после завершения всех задач должен получить локальные результаты и объединить их.
Рекомендуется передавать задачи в порядке возрастания диапазонов:
Результат 1 → диапазон 0
Результат 2 → диапазон 1
Результат 3 → диапазон 2
...
Если каждая задача добавляет индексы в возрастающем порядке, а результаты объединяются в порядке диапазонов, итоговый массив уже будет отсортирован:
[0, 8, 15, 21, 24, 30, 42, 48, 63]
В этом случае дополнительная сортировка результата не требуется.
Arrays.sort() и другие готовые методы сортировки использовать не следует, поскольку порядок можно гарантировать самим алгоритмом объединения.
Потокобезопасность
Мьютексы
Использование мьютексов для результата не требуется.
Не рекомендуется использовать:
synchronized
Lock
ReentrantLock
AtomicInteger
для общего результата.
Причина: потоки не должны изменять одну общую структуру.
Каждый поток работает со своим:
MyList<Integer>
а объединение выполняется после завершения параллельной части одним потоком.
Рекомендуемые классы и импорты
Для реализации рекомендуется использовать стандартные средства Java:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Рекомендуемая схема:
ExecutorService
↓
Callable<MyList<Integer>>
↓
Future<MyList<Integer>>
Каждый Callable обрабатывает свой диапазон и возвращает собственный MyList<Integer>.
После получения всех Future основной метод объединяет результаты.
После завершения работы ExecutorService должен быть корректно закрыт.
При InterruptedException необходимо восстановить статус прерывания текущего потока:
Thread.currentThread().interrupt();
Использование MyList
Сторонние контейнеры не допускаются.
Запрещено использовать:
ArrayList
LinkedList
Vector
CopyOnWriteArrayList
ConcurrentLinkedQueue
и другие стандартные контейнеры
как хранилище промежуточных или итоговых результатов.
Для результатов каждого потока использовать:
MyList<Integer>
Итог преобразовать в:
int[]
Требуется ли изменять MyList
Текущего интерфейса достаточно для реализации задачи:
public interface MyList<T> {
void add(T element);
T get(int index);
T set(int index, T element);
T remove(int index);
int size();
boolean isEmpty();
}
Дополнительные методы добавлять не требуется.
Для задачи достаточно:
size()
get(index)
add(index)
Поскольку поиск выполняется только на чтение, а результаты каждого потока добавляются в его собственный MyList<Integer>, существующего интерфейса достаточно.
Важно
Метод:
void add(T element);
должен добавлять элемент в конец коллекции.
Это позволяет каждому потоку формировать свои индексы в порядке их обнаружения.
Использование Student
Изменения в Student не требуются.
Использовать существующий метод:
equals(Object obj)
Сравнение выполнять:
target.equals(students.get(index))
Не использовать:
==
Не сравнивать объекты вручную по отдельным полям.
Не использовать toString() для определения совпадения.
StudentBuilder
Изменения в StudentBuilder не требуются.
Он не участвует в алгоритме поиска.
Обработка входных данных
Если:
students == null
выбросить:
IllegalArgumentException
Если:
target == null
выбросить:
IllegalArgumentException
Для пустой коллекции вернуть:
new int[0]
Без создания рабочих потоков.
Обобщённый алгоритм реализации
1. Проверить students и target.
2. Получить размер коллекции.
3. Если размер равен 0 → вернуть [].
4. Вычислить количество потоков.
5. Разделить индексы коллекции на непересекающиеся диапазоны.
6. Создать Callable для каждого диапазона.
7. Запустить задачи через ExecutorService.
8. Каждый поток:
- получает fromIndex и toIndex;
- проходит свой диапазон;
- сравнивает Student через equals();
- записывает найденные индексы в собственный MyList<Integer>.
9. Основной поток получает результаты через Future.
10. Объединяет MyList<Integer> в порядке диапазонов.
11. Преобразует результат в int[].
12. Возвращает массив.
Примеры тестов
Одно совпадение
[A, B, C, D]
Искомый:
C
Ожидается:
[2]
Несколько совпадений
[A, B, A, C, A]
Ожидается:
[0, 2, 4]
Нет совпадений
[A, B, C]
Искомый:
D
Ожидается:
[]
Все элементы совпадают
[A, A, A, A]
Ожидается:
[0, 1, 2, 3]
Пустая коллекция
Ожидается:
[]
Один элемент
Проверить:
[A] + A → [0]
[A] + B → []
Проверка границ диапазонов потоков
Проверить коллекции размеров:
1
2
3
4
10
100
1000
Особое внимание уделить совпадениям:
на последнем элементе одного диапазона;
на первом элементе следующего диапазона.
Каждый индекс должен быть найден ровно один раз.
Проверка порядка результата
Создать совпадения в разных диапазонах.
Проверить, что результат имеет вид:
[0, 3, 7, 12, 25, 49, 100]
и находится в порядке возрастания без использования дополнительной сортировки.
Проверка Student.equals()
Создать два разных экземпляра Student с одинаковыми значениями полей.
Один поместить в коллекцию, второй передать как target.
Проверить, что индекс считается совпадением согласно equals().
Проверка неизменности коллекции
После выполнения метода проверить:
- размер
MyList; - порядок элементов;
- значения всех элементов.
Проверка отсутствия вывода
Перехватить стандартный вывод и убедиться, что выполнение findOccurrences() не выводит сообщений в консоль.
Большая коллекция
Создать коллекцию, например из 100000 студентов, с заранее известными индексами совпадений.
Проверить:
- все ожидаемые индексы найдены;
- лишних индексов нет;
- индексы идут по возрастанию;
- итоговый массив имеет правильный размер.
Git
Название ветки:
feature/student-occurrence-index-finder
Результат
Необходимо реализовать:
StudentOccurrenceIndexFinder.java
Существующие интерфейсы и классы:
MyList.java
Student.java
StudentBuilder.java
изменять не требуется.