diff --git a/src/dorkbox/util/collections/LockFreeIntStringMap.java b/src/dorkbox/util/collections/LockFreeIntStringMap.java
new file mode 100644
index 0000000..9f1d222
--- /dev/null
+++ b/src/dorkbox/util/collections/LockFreeIntStringMap.java
@@ -0,0 +1,212 @@
+package dorkbox.util.collections;
+
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+/**
+ * This class uses the "single-writer-principle" for lock-free publication.
+ *
+ * Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
+ * one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
+ * one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
+ * manner.
+ *
+ * According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
+ * contended writes.
+ *
+ * This data structure is for many-read/few-write scenarios
+ *
+ * This is an unordered map that uses int keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash
+ * for problematic keys. Null values are allowed. No allocation is done except when growing the table size.
+ *
+ * This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
+ * depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
+ * next higher POT size.
+ */
+@SuppressWarnings("unchecked")
+public
+class LockFreeIntStringMap {
+ private static final AtomicReferenceFieldUpdater mapREF = AtomicReferenceFieldUpdater.newUpdater(
+ LockFreeIntStringMap.class,
+ IntMap.class,
+ "map");
+
+ private volatile IntMap map;
+
+
+ public LockFreeIntStringMap() {
+ this.map = new IntMap<>();
+ }
+
+ /**
+ * Constructs an empty IntMap with the specified initial
+ * capacity and the default load factor (0.75).
+ *
+ * @param initialCapacity the initial capacity.
+ *
+ * @throws IllegalArgumentException if the initial capacity is negative.
+ */
+ public
+ LockFreeIntStringMap(int initialCapacity) {
+ map = new IntMap<>(initialCapacity);
+ }
+
+ /**
+ * Constructs an empty IntMap with the specified initial
+ * capacity and load factor.
+ *
+ * @param initialCapacity the initial capacity
+ * @param loadFactor the load factor
+ *
+ * @throws IllegalArgumentException if the initial capacity is negative
+ * or the load factor is nonpositive
+ */
+ public
+ LockFreeIntStringMap(int initialCapacity, float loadFactor) {
+ this.map = new IntMap(initialCapacity, loadFactor);
+ }
+
+
+ public
+ int size() {
+ // use the SWP to get a lock-free get of the value
+ return mapREF.get(this)
+ .size;
+ }
+
+ public
+ boolean isEmpty() {
+ // use the SWP to get a lock-free get of the value
+ return mapREF.get(this)
+ .size == 0;
+ }
+
+ public
+ boolean containsKey(final int key) {
+ // use the SWP to get a lock-free get of the value
+ return mapREF.get(this)
+ .containsKey(key);
+ }
+
+ public
+ boolean containsKey(final String key) {
+ // use the SWP to get a lock-free get of the value
+ return mapREF.get(this)
+ .containsKey(key.hashCode());
+ }
+
+ /**
+ * Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
+ * an expensive operation.
+ *
+ * @param identity If true, uses == to compare the specified value with values in the map. If false, uses
+ * {@link #equals(Object)}.
+ */
+ public
+ boolean containsValue(final Object value, boolean identity) {
+ // use the SWP to get a lock-free get of the value
+ return mapREF.get(this)
+ .containsValue(value, identity);
+ }
+
+ public
+ V get(final int key) {
+ // use the SWP to get a lock-free get of the value
+ return (V) mapREF.get(this)
+ .get(key);
+ }
+
+ public
+ V get(final String key) {
+ // use the SWP to get a lock-free get of the value
+ return (V) mapREF.get(this)
+ .get(key.hashCode());
+ }
+
+ public synchronized
+ V put(final int key, final V value) {
+ return map.put(key, value);
+ }
+
+ public synchronized
+ V put(final String key, final V value) {
+ return map.put(key.hashCode(), value);
+ }
+
+ public synchronized
+ V remove(final int key) {
+ return map.remove(key);
+ }
+
+ public synchronized
+ V remove(final String key) {
+ return map.remove(key.hashCode());
+ }
+
+ public synchronized
+ void putAll(final IntMap map) {
+ this.map.putAll(map);
+ }
+
+ /**
+ * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
+ *
+ * Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
+ * time this method is called. Use the {@link IntMap.Entries} constructor for nested or multithreaded iteration.
+ */
+ public
+ IntMap.Keys keys() {
+ return mapREF.get(this)
+ .keys();
+ }
+
+ /**
+ * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
+ *
+ * Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
+ * time this method is called. Use the {@link IntMap.Entries} constructor for nested or multithreaded iteration.
+ */
+ public
+ IntMap.Values values() {
+ return mapREF.get(this)
+ .values();
+ }
+
+ /**
+ * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
+ *
+ * Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
+ * time this method is called. Use the {@link IntMap.Entries} constructor for nested or multithreaded iteration.
+ */
+ public
+ IntMap.Entries entries() {
+ return mapREF.get(this)
+ .entries();
+ }
+
+ public synchronized
+ void clear() {
+ map.clear();
+ }
+
+ /**
+ * Identity equals only!
+ */
+ @Override
+ public
+ boolean equals(final Object o) {
+ return this == o;
+ }
+
+ @Override
+ public
+ int hashCode() {
+ return mapREF.get(this)
+ .hashCode();
+ }
+
+ @Override
+ public
+ String toString() {
+ return mapREF.get(this)
+ .toString();
+ }
+}