Выбор структуры определяется характером нагрузки:
Для словарей почти всегда подойдёт ConcurrentHashMap, если не нужен строгий порядок ключей (тогда обратите внимание на ConcurrentSkipListMap).
Для счётчиков — LongAdder при высоком уровне конкуренции, AtomicLong — если число потоков невелико или нужна мгновенная атомарность.
Для очередей — ConcurrentLinkedQueue в неблокирующих сценариях, а ArrayBlockingQueue или LinkedBlockingQueue — если требуется ограничить размер и организовать ожидание.
Важная тонкость: даже неблокирующие структуры могут страдать от ложного разделения (false sharing), когда несколько переменных попадают в одну кэш-линию процессора. Для таких случаев предусмотрена аннотация @Contended (начиная с Java 8, требуется отключить ограничение через -XX:-RestrictContended). Она помогает разнести поля по разным кэш-линиям и избежать лишней синхронизации на уровне аппаратуры.