Я не вижу, где текущий код изменяет набор. Таким образом, элемент объекта nodeRefsWithTags
кажется доступным для других потоков, и должен существовать другой параллельный поток, модифицирующий его. При потоковой передаче заданных элементов нет никакой гарантии, что никакие изменения не будут сделаны. Есть несколько вариантов:
Создать копию:
// At this point, you have to make sure nodeRefsWithTags is not modified by other threads.
Set<...> copy = new HashSet<>(nodeRefsWithTags);
// From now on it's ok to modify the original set
List<Tag> tags = copy.stream()...
Используйте ConcurrentHashMap
:
// For this option you have to make sure that no elements get removed from the map, else you
// might get an endless loop (!) which is very hard to find and may occur occationally only.
nodeRefsWithTags = Collections.newSetFromMap(new ConcurrentHashMap<>());
Или используйте обычную синхронизацию (для всех модификаций требуется блокировка, а также код, который вы разместили).