Thread-safe concurrent collection implementations with atomic operations backed by ReadWriteLock. Provides concurrent variants of List, Map, Set, Deque, and Queue, plus tuple types (Pair, Triple, Single), sorted collection views, unmodifiable wrappers, and searchable/queryable interfaces.
Important
This library requires Java 21 or later. It is published via JitPack and is intended as a foundational dependency for other Simplified-Dev modules.
- Atomic bases -
AtomicCollection,AtomicList,AtomicSet,AtomicMap,AtomicQueue,AtomicDeque, plus the navigable basesAtomicNavigableSetandAtomicNavigableMap. Each is the canonical extension point for custom concurrent types and uses aReadWriteLockfor atomic guarantees. - Concurrent interfaces -
ConcurrentCollection,ConcurrentList,ConcurrentMap,ConcurrentSet,ConcurrentDeque, andConcurrentQueuecarry only contract methods - no nestedImpl, no static factories. - Public impl classes - 10 concrete classes named after the JDK type they wrap:
ConcurrentArrayList,ConcurrentLinkedList,ConcurrentHashSet,ConcurrentLinkedSet,ConcurrentTreeSet,ConcurrentHashMap,ConcurrentLinkedMap,ConcurrentTreeMap,ConcurrentArrayQueue,ConcurrentArrayDeque. Construct directly withnew(extra knobs like initial capacity, comparator, or max size are constructor params) or viaXxx.adopt(backing)for zero-copy publication. - Linked variants -
ConcurrentLinkedList(extendsAtomicListdirectly, mirroring the JDK),ConcurrentLinkedSet extends ConcurrentHashSet,ConcurrentLinkedMap extends ConcurrentHashMap(insertion-ordered;ConcurrentLinkedMapsupports an optional eldest-entry eviction cap via constructor). - Tree variants -
ConcurrentTreeMapandConcurrentTreeSetfor sorted/navigable views; backed by aSortedSnapshotSpliteratorexposingSORTED | ORDERED | DISTINCTcharacteristics. - Unmodifiable snapshots - Immutable wrappers live as package-private nested classes inside
ConcurrentUnmodifiable(mirrorsCollections.UnmodifiableMap). Pair an immutable snapshot withNoOpReadWriteLockfor wait-free reads; obtain viatoUnmodifiable()on any mutable instance orConcurrent.newUnmodifiableX(...).NoOpReadWriteLockisSerializableso snapshots round-trip through serialization without losing their lock semantics. Concurrentfactory hub - StaticnewX(...),toX(...),adoptX(...), andnewUnmodifiableX(...)helpers organized alphabetically; return types narrow to the concrete impl class (e.g.newLinkedMapreturnsConcurrentLinkedMap<K,V>).ReadWriteLock-based concurrency -withReadLock/withWriteLocklambda helpers onAtomicCollectionandAtomicMapwrap the lock + snapshot-invalidation pattern uniformly.AtomicMapexposes acheckMutationAllowedhook subclasses override to gate writes.- Tuple types -
Pair,Triple, andSinglewith mutable/immutable variants and streaming support (PairStream,TripleStream,SingleStream,LifecycleSingleStream). - Searchable / Sortable interfaces - Generic query abstractions backing the search and sort surfaces.
- Pluggable sorting algorithms -
SortAlgorithm<E>@FunctionalInterfaceplugged intoAtomicList.sorted(...). Pre-built strategies onComparison(timsort/heap/insertion/shell/quicksort/mergesort),RadixSort(byInt/byLong), andCountingSort(byIntfor bounded ranges).Graph.asLinearSort()/asLayeredSort()project graph topology asSortAlgorithmstrategies. See Sorting Algorithms below. - Graph dependency ordering -
Graphexposes linear and layered topological sorts (iterative DFS post-order for sequential workloads, Kahn's BFS for parallel scheduling) plus structural queries (successors,predecessors,roots,leaves,reverse). - Stream utilities -
StreamUtilfor enhanced stream operations includingdistinctByKey(parallel + sequential variants). - Functional interfaces -
TriConsumer,TriFunction,TriPredicate,ToInt/Long/DoubleTriFunction,QuadFunction, plusIndexedConsumer/Function/Predicate. - Optional Gson SPI -
ConcurrentTypeAdapterFactoryregistered viaMETA-INF/servicesso consumers with Gson on the classpath get JSON support automatically; Gson iscompileOnly, never pulled into runtime otherwise.
| Requirement | Version | Notes |
|---|---|---|
| Java | 21+ | Required |
| Gradle | 9.4+ | Included via wrapper |
| Git | 2.x+ | For cloning the repository |
Add the JitPack repository and dependency to your build.gradle.kts:
repositories {
maven(url = "https://jitpack.io")
}
dependencies {
implementation("com.github.simplified-dev:collections:master-SNAPSHOT")
}Groovy DSL
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.simplified-dev:collections:master-SNAPSHOT'
}Maven
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.simplified-dev</groupId>
<artifactId>collections</artifactId>
<version>master-SNAPSHOT</version>
</dependency>Create thread-safe collections by instantiating the public concrete classes directly or via the Concurrent factory hub:
import dev.simplified.collection.Concurrent;
import dev.simplified.collection.ConcurrentArrayList;
import dev.simplified.collection.ConcurrentHashMap;
import dev.simplified.collection.ConcurrentHashSet;
import dev.simplified.collection.ConcurrentLinkedMap;
import dev.simplified.collection.ConcurrentTreeMap;
// Construct via the public impl classes
ConcurrentArrayList<String> list = new ConcurrentArrayList<>();
ConcurrentArrayList<String> seed = new ConcurrentArrayList<>("a", "b", "c");
ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<>(existingMap);
ConcurrentHashSet<String> set = new ConcurrentHashSet<>();
ConcurrentTreeMap<String, Long> tree = new ConcurrentTreeMap<>(byPriority);
ConcurrentLinkedMap<String, Integer> lru = new ConcurrentLinkedMap<>(/* maxSize */ 1_000);
// Or go through the Concurrent factory hub (return types narrow to the concrete impl)
ConcurrentArrayList<String> via = Concurrent.newList("a", "b");
ConcurrentLinkedMap<String, Long> lhm = Concurrent.newLinkedMap();
// Zero-copy publication of a single-threaded build result
ConcurrentArrayList<String> adopted = ConcurrentArrayList.adopt(prebuilt);
list.add("hello");
map.put("key", 42L);
set.add("unique");Tip
All concurrent collections use ReadWriteLock internally, allowing multiple
concurrent readers while ensuring exclusive write access. Call
toUnmodifiable() on any mutable instance to take a wait-free immutable
snapshot backed by NoOpReadWriteLock - snapshots are Serializable and
round-trip without losing their lock semantics.
Stream-friendly tuple abstractions with mutable and immutable variants:
import dev.simplified.collection.tuple.pair.Pair;
import dev.simplified.collection.tuple.pair.PairStream;
import dev.simplified.collection.tuple.triple.Triple;
// Immutable pair
Pair<String, Integer> pair = Pair.of("name", 42);
// Stream of pairs
PairStream.of(map)
.filter((key, value) -> value > 10)
.forEach((key, value) -> System.out.println(key + "=" + value));
// Triple
Triple<String, Integer, Boolean> triple = Triple.of("name", 42, true);AtomicList (and its ConcurrentArrayList / ConcurrentLinkedList impls) hardcodes JDK Timsort via sorted(Comparator). The pluggable sorted(SortAlgorithm) overload lets you opt into a workload-specific algorithm when Timsort isn't the right fit. The SortAlgorithm<E> type is a @FunctionalInterface so it accepts lambdas and pre-built factories alike:
import dev.simplified.collection.ConcurrentArrayList;
import dev.simplified.collection.sort.Comparison;
import dev.simplified.collection.sort.CountingSort;
import dev.simplified.collection.sort.RadixSort;
ConcurrentArrayList<Integer> ids = new ConcurrentArrayList<>(/* large list of ints */);
// Default Timsort — sorted(Comparator) routes through Comparison.timsort internally
ConcurrentList<Integer> a = ids.sorted(Integer::compare);
// LSD base-256 radix on int keys — ~10× faster than Timsort once n >= 100k
ConcurrentList<Integer> b = ids.sorted(RadixSort.byInt(Integer::intValue));
// Counting sort — ~15-20× faster than Timsort when the key range is small
ConcurrentList<Integer> c = ids.sorted(CountingSort.byInt(Integer::intValue, 0, 99));
// Custom one-off via lambda
ConcurrentList<Integer> d = ids.sorted(list -> list.sort(Comparator.reverseOrder()));Algorithm families (full Time/Space complexity in each method's javadoc):
| Family | Static factory | Best for |
|---|---|---|
| Comparison | timsort(cmp) |
Default; partially-sorted real-world data |
heap(cmp) |
Strict O(n log n) worst case, low memory | |
insertion(cmp) |
Tiny inputs (n ≤ ~32) | |
shell(cmp) |
Mid-sized arrays, low memory | |
quicksort(cmp) |
Random data, no stability needed | |
mergesort(cmp) |
Stability + worst-case guarantee | |
| RadixSort | byInt(keyFn) |
Large int-keyed data (n ≥ ~10k) |
byLong(keyFn) |
Long-keyed data at small-medium n (~1k); ties Timsort at scale | |
| CountingSort | byInt(keyFn, min, max) |
Bounded-range int data (small max - min) |
Graph projects its topological order as a SortAlgorithm, letting you reorder arbitrary subsets by graph dependencies without re-running the sort:
import dev.simplified.collection.sort.Graph;
Graph<EntityType> dependencies = Graph.<EntityType>builder()
.withValues(allEntities)
.withEdgeFunction(entity -> entity.referencedTypes())
.build();
// Reusable algorithm — index map computed once at construction
SortAlgorithm<EntityType> dependencyOrder = dependencies.asLinearSort();
// Apply to any subset
List<EntityType> subset = userSelection();
dependencyOrder.sort(subset);
// Or feed straight into AtomicList.sorted
ConcurrentList<EntityType> sorted = concurrentEntities.sorted(dependencyOrder);asLayeredSort() is the Kahn's-algorithm variant that buckets by topological layer while preserving each layer's input order via Timsort stability — useful when the input list already has a meaningful priority and you only need layer-respecting bucketing.
Tip
Empirically measured deltas (random int data): radix beats Timsort by ~10× at n ≥ 100k; counting sort beats Timsort by ~15-20× on bounded ranges (k=100); insertion sort beats Timsort by ~30% at n ≤ 32; Comparison.timsort is byte-zero overhead vs raw list.sort(cmp). The Comparison/RadixSort/CountingSort algorithm bodies all use ListIterator-based writeback so they're O(n) on LinkedList-backed lists, not O(n²).
collections/
├── src/
│ ├── main/java/dev/simplified/collection/
│ │ ├── atomic/ # AtomicCollection, AtomicList, AtomicSet, AtomicMap,
│ │ │ # AtomicQueue, AtomicDeque, AtomicNavigableSet,
│ │ │ # AtomicNavigableMap, AtomicIterator,
│ │ │ # SortedSnapshotSpliterator
│ │ ├── function/ # TriConsumer/Function/Predicate, ToInt/Long/DoubleTriFunction,
│ │ │ # QuadFunction, IndexedConsumer/Function/Predicate
│ │ ├── gson/ # ConcurrentTypeAdapterFactory (opt-in Gson SPI)
│ │ ├── query/ # Searchable, SearchFunction, Sortable, SortOrder
│ │ ├── sort/ # SortAlgorithm (FunctionalInterface), Comparison,
│ │ │ # RadixSort, CountingSort, Graph (linear/layered topo
│ │ │ # sort + asLinearSort/asLayeredSort projections)
│ │ ├── tuple/
│ │ │ ├── pair/ # Pair, ImmutablePair, MutablePair, PairOptional, PairStream
│ │ │ ├── single/ # SingleStream, LifecycleSingleStream
│ │ │ └── triple/ # Triple, ImmutableTriple, MutableTriple, TripleStream
│ │ ├── Concurrent.java # Factory hub; return types narrow to concrete impls
│ │ ├── ConcurrentUnmodifiable.java # Package-private snapshot wrappers + NoOpReadWriteLock
│ │ ├── ConcurrentCollection.java, ConcurrentList.java, ConcurrentSet.java, ConcurrentMap.java
│ │ ├── ConcurrentQueue.java, ConcurrentDeque.java # base interfaces
│ │ ├── ConcurrentArrayList.java, ConcurrentLinkedList.java # list impls
│ │ ├── ConcurrentHashSet.java, ConcurrentLinkedSet.java, ConcurrentTreeSet.java
│ │ ├── ConcurrentHashMap.java, ConcurrentLinkedMap.java, ConcurrentTreeMap.java
│ │ ├── ConcurrentArrayQueue.java, ConcurrentArrayDeque.java
│ │ └── StreamUtil.java # Stream utility methods
│ ├── test/java/ # JUnit 5 tests (ConcurrentListTest, ConcurrentMapTest, etc.)
│ └── jmh/java/ # 16 JMH benchmarks: per-impl + contention + 4 sort suites
│ # (SortAlgorithm, SortAlgorithmLong, SortAlgorithmTiny,
│ # SortAlgorithmBoundedRange)
├── build.gradle.kts
├── settings.gradle.kts
└── LICENSE.md
Build the project using the Gradle wrapper:
./gradlew build# Run standard unit tests (excludes slow tests)
./gradlew test
# Run slow integration tests (shutdown, thread leak detection)
./gradlew slowTestNote
The default test task excludes tests tagged with @Tag("slow").
Use slowTest to run long-running integration tests separately.
Run JMH benchmarks for concurrent collection performance:
./gradlew jmhBenchmarks cover every concrete impl (ConcurrentArrayList, ConcurrentLinkedList,
ConcurrentHashSet, ConcurrentLinkedSet, ConcurrentTreeSet, ConcurrentHashMap,
ConcurrentLinkedMap, ConcurrentTreeMap, ConcurrentArrayQueue, ConcurrentArrayDeque),
list/map contention scenarios under concurrent access, plus a four-class suite for the
Sorting Algorithms: random ints, random longs, tiny inputs (insertion's
sweet spot), and bounded-range inputs (counting sort's sweet spot).
For focused runs, pass Gradle properties to filter or tune the JMH harness:
./gradlew jmh -PjmhInclude=ConcurrentTreeMapBenchmark -PjmhFork=1 -PjmhWarmup=2 -PjmhIter=3| Property | Effect |
|---|---|
-PjmhInclude=<regex> |
Run only benchmarks whose class name matches the regex |
-PjmhFork=<n> |
Override the JVM fork count |
-PjmhWarmup=<n> |
Override warm-up iterations |
-PjmhIter=<n> |
Override measurement iterations |
See CONTRIBUTING.md for development setup, code style guidelines, and how to submit a pull request.
This project is licensed under the Apache License 2.0 - see LICENSE.md for the full text.