Я создаю Java-класс IdGenerator, который выделяет уникальный целочисленный идентификатор каждый раз, когда он запрашивается. Он использует TreeSet для хранения диапазонов свободных идентификаторов, и каждый раз, когда запрашивается идентификатор, он просматривает набор, чтобы найти диапазон, выделяет первый идентификатор в диапазоне, удаляет диапазон и добавляет новый диапазон, который на один меньше. Весь процесс распределения синхронизируется на наборе, чтобы гарантировать, что разные потоки не конфликтуют.
Это работало нормально, когда я проводил модульное тестирование класса, но я только что запустил тест другого класса, в котором экземпляр класса IdGenerator вызывается десять раз подряд, разными потоками, и каждый раз возвращает одно и то же значение. Ведение журнала показывает, что при каждом вызове набор, содержащий свободные диапазоны, имеет одинаковое содержимое, хотя переменная lastId отличается: -1 при первом вызове, 0 для остальных. Похоже, это говорит о том, что разные потоки используют разные копии набора, хотя это не то, что я ожидал бы от кода.
Я использую JRE 1.8.0_191 в Eclipse Neon 4.6.3 в Windows 10.
Я пробовал синхронизировать объект-генератор, а не набор, заключив TreeSet в synchronizedSortedSet и используя объект Lock вместо ключевого слова synchronized. Ничего из этого не имело никакого значения.
private final SortedSet<Range> freeRanges = new TreeSet<>();
private int lastId;
public int allocateId() throws IllegalStateException
{
int answer;
synchronized (freeRanges)
{
LOG.debug("lastId = {}, freeRanges = {}", lastId, freeRanges);
if (freeRanges.isEmpty())
throw new IllegalStateException("All possible IDs are allocated");
Range range = Stream
.of(freeRanges.tailSet(new Range(lastId + 1)), freeRanges)
.filter(s -> !s.isEmpty())
.map(SortedSet::first)
.findFirst()
.get();
answer = lastId = range.start;
freeRanges.remove(range);
if (range.start != range.end)
freeRanges.add(new Range(range.start + 1, range.end));
LOG.debug("Allocated {}, freeRanges = {}", answer, freeRanges);
}
return answer;
}
Вывод журнала показан ниже. Я ожидаю, что при n-м вызове выделенное число будет n-1, а набор свободных диапазонов обновится, чтобы показать диапазон, начинающийся с n и заканчивающийся 100. Но вместо этого я вижу следующее:
16:03:18.554 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = -1, freeRanges = [Range [start=0, end=100]]
16:03:18.570 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
freeRanges.first()
? - person Marco   schedule 08.01.2019synchronized (this)
, а неsynchronized (freeRanges)
. Я не поклонникsynchronized (this)
, потому что это означает, что внешний код также может синхронизироваться с моим объектом; Я предпочитаю синхронизировать объект, о котором знаю только я, - person Jeremy Hicks   schedule 08.01.2019freeRanges.first()
, и это не повлияло на проблему. - person Jeremy Hicks   schedule 08.01.2019Queue<Range>
(или дажеDeque<Range>
) и удалить диапазон, а затем добавить его в конец очереди? Это также означало бы, что вам не нужно было бы синхронизировать тело метода, если бы вы использовали потокобезопасную реализацию очереди. - person Daniel Pryden   schedule 08.01.2019Range
, чтобы мы могли запустить его локально? - person Makoto   schedule 08.01.2019Queue<Range>
усложнит ведение списка свободных диапазонов, когда он становится более фрагментированным, а также затруднит достижение цели циклического перебора всех возможных значений перед повторным запуском с самого начала. - person Jeremy Hicks   schedule 08.01.2019