diff --git a/pom.xml b/pom.xml
index 612a426..4613cc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,7 +45,7 @@
org.testng
testng
- 6.8.8
+ 7.5.1
test
diff --git a/src/main/java/net/jodah/expiringmap/ExpiringEntry.java b/src/main/java/net/jodah/expiringmap/ExpiringEntry.java
new file mode 100644
index 0000000..9450dc8
--- /dev/null
+++ b/src/main/java/net/jodah/expiringmap/ExpiringEntry.java
@@ -0,0 +1,113 @@
+package net.jodah.expiringmap;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Expiring map entry implementation. */
+class ExpiringEntry implements Comparable> {
+ final AtomicLong expirationNanos;
+ /** Epoch time at which the entry is expected to expire */
+ final AtomicLong expectedExpiration;
+ final AtomicReference expirationPolicy;
+ final K key;
+ /** Guarded by "this" */
+ volatile Future> entryFuture;
+ /** Guarded by "this" */
+ V value;
+ /** Guarded by "this" */
+ volatile boolean scheduled;
+
+ /**
+ * Creates a new ExpiringEntry object.
+ *
+ * @param key for the entry
+ * @param value for the entry
+ * @param expirationPolicy for the entry
+ * @param expirationNanos for the entry
+ */
+ ExpiringEntry(K key, V value, AtomicReference expirationPolicy, AtomicLong expirationNanos) {
+ this.key = key;
+ this.value = value;
+ this.expirationPolicy = expirationPolicy;
+ this.expirationNanos = expirationNanos;
+ this.expectedExpiration = new AtomicLong();
+ resetExpiration();
+ }
+
+ @Override
+ public int compareTo(ExpiringEntry other) {
+ if (key.equals(other.key))
+ return 0;
+ return expectedExpiration.get() < other.expectedExpiration.get() ? -1 : 1;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((key == null) ? 0 : key.hashCode());
+ result = prime * result + ((value == null) ? 0 : value.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ExpiringEntry, ?> other = (ExpiringEntry, ?>) obj;
+ if (!key.equals(other.key))
+ return false;
+ if (value == null) {
+ if (other.value != null)
+ return false;
+ } else if (!value.equals(other.value))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ /**
+ * Marks the entry as canceled.
+ *
+ * @return true if the entry was scheduled
+ */
+ synchronized boolean cancel() {
+ boolean result = scheduled;
+ if (entryFuture != null)
+ entryFuture.cancel(false);
+
+ entryFuture = null;
+ scheduled = false;
+ return result;
+ }
+
+ /** Gets the entry value. */
+ synchronized V getValue() {
+ return value;
+ }
+
+ /** Resets the entry's expected expiration. */
+ void resetExpiration() {
+ expectedExpiration.set(expirationNanos.get() + System.nanoTime());
+ }
+
+ /** Marks the entry as scheduled. */
+ synchronized void schedule(Future> entryFuture) {
+ this.entryFuture = entryFuture;
+ scheduled = true;
+ }
+
+ /** Sets the entry value. */
+ synchronized void setValue(V value) {
+ this.value = value;
+ }
+}
diff --git a/src/main/java/net/jodah/expiringmap/ExpiringMap.java b/src/main/java/net/jodah/expiringmap/ExpiringMap.java
index 26dc4e9..195dcb5 100644
--- a/src/main/java/net/jodah/expiringmap/ExpiringMap.java
+++ b/src/main/java/net/jodah/expiringmap/ExpiringMap.java
@@ -20,14 +20,14 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.jodah.expiringmap.internal.Assert;
import net.jodah.expiringmap.internal.NamedThreadFactory;
@@ -81,7 +81,7 @@ public class ExpiringMap implements ConcurrentMap {
List> expirationListeners;
List> asyncExpirationListeners;
- private AtomicLong expirationNanos;
+ private AtomicLong defaultExpirationDurationNanos;
private int maxSize;
private final AtomicReference expirationPolicy;
private final EntryLoader super K, ? extends V> entryLoader;
@@ -129,7 +129,7 @@ private ExpiringMap(final Builder builder) {
if (builder.asyncExpirationListeners != null)
asyncExpirationListeners = new CopyOnWriteArrayList>(builder.asyncExpirationListeners);
expirationPolicy = new AtomicReference(builder.expirationPolicy);
- expirationNanos = new AtomicLong(TimeUnit.NANOSECONDS.convert(builder.duration, builder.timeUnit));
+ defaultExpirationDurationNanos = new AtomicLong(TimeUnit.NANOSECONDS.convert(builder.duration, builder.timeUnit));
maxSize = builder.maxSize;
entryLoader = builder.entryLoader;
expiringEntryLoader = builder.expiringEntryLoader;
@@ -320,9 +320,9 @@ private void assertNoLoaderSet() {
}
/** Entry map definition. */
- private interface EntryMap extends Map> {
- /** Returns the first entry in the map or null if the map is empty. */
- ExpiringEntry first();
+ private interface EntryMap extends Map> {
+ /** Returns the next entry to expire in the map or null if the map is empty. */
+ ExpiringEntry getNextToExpire();
/**
* Reorders the given entry in the map.
@@ -351,9 +351,9 @@ public boolean containsValue(Object value) {
}
@Override
- public ExpiringEntry first() {
- return isEmpty() ? null : values().iterator().next();
- }
+ public ExpiringEntry getNextToExpire() {
+ return isEmpty() ? null : values().iterator().next();
+ }
@Override
public void reorder(ExpiringEntry value) {
@@ -431,9 +431,9 @@ public boolean containsValue(Object value) {
}
@Override
- public ExpiringEntry first() {
- return sortedSet.isEmpty() ? null : sortedSet.first();
- }
+ public ExpiringEntry getNextToExpire() {
+ return sortedSet.isEmpty() ? null : sortedSet.first();
+ }
@Override
public ExpiringEntry put(K key, ExpiringEntry value) {
@@ -505,114 +505,6 @@ public final Map.Entry next() {
}
}
- /** Expiring map entry implementation. */
- static class ExpiringEntry implements Comparable> {
- final AtomicLong expirationNanos;
- /** Epoch time at which the entry is expected to expire */
- final AtomicLong expectedExpiration;
- final AtomicReference expirationPolicy;
- final K key;
- /** Guarded by "this" */
- volatile Future> entryFuture;
- /** Guarded by "this" */
- V value;
- /** Guarded by "this" */
- volatile boolean scheduled;
-
- /**
- * Creates a new ExpiringEntry object.
- *
- * @param key for the entry
- * @param value for the entry
- * @param expirationPolicy for the entry
- * @param expirationNanos for the entry
- */
- ExpiringEntry(K key, V value, AtomicReference expirationPolicy, AtomicLong expirationNanos) {
- this.key = key;
- this.value = value;
- this.expirationPolicy = expirationPolicy;
- this.expirationNanos = expirationNanos;
- this.expectedExpiration = new AtomicLong();
- resetExpiration();
- }
-
- @Override
- public int compareTo(ExpiringEntry other) {
- if (key.equals(other.key))
- return 0;
- return expectedExpiration.get() < other.expectedExpiration.get() ? -1 : 1;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((key == null) ? 0 : key.hashCode());
- result = prime * result + ((value == null) ? 0 : value.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- ExpiringEntry, ?> other = (ExpiringEntry, ?>) obj;
- if (!key.equals(other.key))
- return false;
- if (value == null) {
- if (other.value != null)
- return false;
- } else if (!value.equals(other.value))
- return false;
- return true;
- }
-
- @Override
- public String toString() {
- return value.toString();
- }
-
- /**
- * Marks the entry as canceled.
- *
- * @return true if the entry was scheduled
- */
- synchronized boolean cancel() {
- boolean result = scheduled;
- if (entryFuture != null)
- entryFuture.cancel(false);
-
- entryFuture = null;
- scheduled = false;
- return result;
- }
-
- /** Gets the entry value. */
- synchronized V getValue() {
- return value;
- }
-
- /** Resets the entry's expected expiration. */
- void resetExpiration() {
- expectedExpiration.set(expirationNanos.get() + System.nanoTime());
- }
-
- /** Marks the entry as scheduled. */
- synchronized void schedule(Future> entryFuture) {
- this.entryFuture = entryFuture;
- scheduled = true;
- }
-
- /** Sets the entry value. */
- synchronized void setValue(V value) {
- this.value = value;
- }
- }
-
/**
* Creates an ExpiringMap builder.
*
@@ -659,36 +551,33 @@ public synchronized void addAsyncExpirationListener(ExpirationListener lis
initListenerService();
}
- @Override
- public void clear() {
- writeLock.lock();
- try {
- for (ExpiringEntry entry : entries.values())
- entry.cancel();
- entries.clear();
- } finally {
- writeLock.unlock();
- }
- }
-
- @Override
- public boolean containsKey(Object key) {
- readLock.lock();
- try {
- return entries.containsKey(key);
- } finally {
- readLock.unlock();
- }
- }
+ @Override
+ public void clear() {
+ withWriteLock(new Runnable() {
+ public void run() {
+ for (ExpiringEntry entry : entries.values())
+ entry.cancel();
+ entries.clear();
+ }
+ });
+ }
@Override
- public boolean containsValue(Object value) {
- readLock.lock();
- try {
- return entries.containsValue(value);
- } finally {
- readLock.unlock();
- }
+ public boolean containsKey(final Object key) {
+ return withReadLock(new LockOperation() {
+ public Boolean get() {
+ return entries.containsKey(key);
+ }
+ });
+ }
+
+ @Override
+ public boolean containsValue(final Object value) {
+ return withReadLock(new LockOperation() {
+ public Boolean get() {
+ return entries.containsValue(value);
+ }
+ });
}
/**
@@ -704,13 +593,13 @@ public void clear() {
ExpiringMap.this.clear();
}
- @Override
- public boolean contains(Object entry) {
- if (!(entry instanceof Map.Entry))
- return false;
- Map.Entry, ?> e = (Map.Entry, ?>) entry;
- return containsKey(e.getKey());
- }
+ @Override
+ public boolean contains(Object entry) {
+ if (!(entry instanceof Map.Entry))
+ return false;
+ Map.Entry, ?> mapEntry = (Map.Entry, ?>) entry;
+ return containsKey(mapEntry.getKey());
+ }
@Override
public Iterator> iterator() {
@@ -723,14 +612,14 @@ public Iterator> iterator() {
}
}
- @Override
- public boolean remove(Object entry) {
- if (entry instanceof Map.Entry) {
- Map.Entry, ?> e = (Map.Entry, ?>) entry;
- return ExpiringMap.this.remove(e.getKey()) != null;
- }
- return false;
- }
+ @Override
+ public boolean remove(Object entry) {
+ if (entry instanceof Map.Entry) {
+ Map.Entry, ?> mapEntry = (Map.Entry, ?>) entry;
+ return ExpiringMap.this.remove(mapEntry.getKey()) != null;
+ }
+ return false;
+ }
@Override
public int size() {
@@ -740,27 +629,31 @@ public int size() {
}
@Override
- public boolean equals(Object obj) {
- readLock.lock();
- try {
- return entries.equals(obj);
- } finally {
- readLock.unlock();
- }
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public V get(Object key) {
- ExpiringEntry entry = getEntry(key);
-
- if (entry == null) {
- return load((K) key);
- } else if (ExpirationPolicy.ACCESSED.equals(entry.expirationPolicy.get()))
- resetEntry(entry, false);
-
- return entry.getValue();
- }
+ public boolean equals(final Object obj) {
+ return withReadLock(new LockOperation() {
+ public Boolean get() {
+ return entries.equals(obj);
+ }
+ });
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public V get(Object key) {
+ ExpiringEntry entry = getEntry(key);
+
+ if (entry == null)
+ return load((K) key);
+
+ if (isAccessBased(entry))
+ resetEntry(entry, false);
+
+ return entry.getValue();
+ }
+
+ private boolean isAccessBased(ExpiringEntry entry) {
+ return ExpirationPolicy.ACCESSED.equals(entry.expirationPolicy.get());
+ }
private V load(K key) {
if (entryLoader == null && expiringEntryLoader == null)
@@ -783,7 +676,8 @@ private V load(K key) {
put(key, null);
return null;
} else {
- long duration = expiringValue.getTimeUnit() == null ? expirationNanos.get() : expiringValue.getDuration();
+ long duration = expiringValue.getTimeUnit() == null ? defaultExpirationDurationNanos.get()
+ : expiringValue.getDuration();
TimeUnit timeUnit = expiringValue.getTimeUnit() == null ? TimeUnit.NANOSECONDS : expiringValue.getTimeUnit();
put(key, expiringValue.getValue(), expiringValue.getExpirationPolicy() == null ? expirationPolicy.get()
: expiringValue.getExpirationPolicy(), duration, timeUnit);
@@ -801,7 +695,7 @@ private V load(K key) {
* @return The expiration duration (milliseconds)
*/
public long getExpiration() {
- return TimeUnit.NANOSECONDS.toMillis(expirationNanos.get());
+ return TimeUnit.NANOSECONDS.toMillis(defaultExpirationDurationNanos.get());
}
/**
@@ -861,23 +755,21 @@ public int getMaxSize() {
}
@Override
- public int hashCode() {
- readLock.lock();
- try {
- return entries.hashCode();
- } finally {
- readLock.unlock();
- }
- }
-
- @Override
- public boolean isEmpty() {
- readLock.lock();
- try {
- return entries.isEmpty();
- } finally {
- readLock.unlock();
- }
+ public int hashCode() {
+ return withReadLock(new LockOperation() {
+ public Integer get() {
+ return entries.hashCode();
+ }
+ });
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return withReadLock(new LockOperation() {
+ public Boolean get() {
+ return entries.isEmpty();
+ }
+ });
}
/**
@@ -931,17 +823,21 @@ public int size() {
* @throws NullPointerException if {@code key} is null
*/
@Override
- public V put(K key, V value) {
- Assert.notNull(key, "key");
- return putInternal(key, value, expirationPolicy.get(), expirationNanos.get());
- }
+ public V put(final K key, final V value) {
+ Assert.notNull(key, "key");
+ return withWriteLock(new LockOperation() {
+ public V get() {
+ return putInternal(key, value, expirationPolicy.get(), defaultExpirationDurationNanos.get());
+ }
+ });
+ }
/**
* @see #put(Object, Object, ExpirationPolicy, long, TimeUnit)
*/
- public V put(K key, V value, ExpirationPolicy expirationPolicy) {
- return put(key, value, expirationPolicy, expirationNanos.get(), TimeUnit.NANOSECONDS);
- }
+ public V put(K key, V value, ExpirationPolicy expirationPolicy) {
+ return put(key, value, expirationPolicy, defaultExpirationDurationNanos.get(), TimeUnit.NANOSECONDS);
+ }
/**
* @see #put(Object, Object, ExpirationPolicy, long, TimeUnit)
@@ -962,18 +858,23 @@ public V put(K key, V value, long duration, TimeUnit timeUnit) {
* @throws UnsupportedOperationException If variable expiration is not enabled
* @throws NullPointerException if {@code key}, {@code expirationPolicy} or {@code timeUnit} are null
*/
- public V put(K key, V value, ExpirationPolicy expirationPolicy, long duration, TimeUnit timeUnit) {
- Assert.notNull(key, "key");
- Assert.notNull(expirationPolicy, "expirationPolicy");
- Assert.notNull(timeUnit, "timeUnit");
- Assert.operation(variableExpiration, "Variable expiration is not enabled");
- return putInternal(key, value, expirationPolicy, TimeUnit.NANOSECONDS.convert(duration, timeUnit));
- }
+ public V put(final K key, final V value, final ExpirationPolicy expirationPolicy, long duration, TimeUnit timeUnit) {
+ Assert.notNull(key, "key");
+ Assert.notNull(expirationPolicy, "expirationPolicy");
+ Assert.notNull(timeUnit, "timeUnit");
+ Assert.operation(variableExpiration, "Variable expiration is not enabled");
+ final long expirationNanos = TimeUnit.NANOSECONDS.convert(duration, timeUnit);
+ return withWriteLock(new LockOperation() {
+ public V get() {
+ return putInternal(key, value, expirationPolicy, expirationNanos);
+ }
+ });
+ }
@Override
public void putAll(Map extends K, ? extends V> map) {
Assert.notNull(map, "map");
- long expiration = expirationNanos.get();
+ long expiration = defaultExpirationDurationNanos.get();
ExpirationPolicy expirationPolicy = this.expirationPolicy.get();
writeLock.lock();
try {
@@ -990,7 +891,7 @@ public V putIfAbsent(K key, V value) {
writeLock.lock();
try {
if (!entries.containsKey(key))
- return putInternal(key, value, expirationPolicy.get(), expirationNanos.get());
+ return putInternal(key, value, expirationPolicy.get(), defaultExpirationDurationNanos.get());
else
return entries.get(key).getValue();
} finally {
@@ -1007,7 +908,7 @@ public V remove(Object key) {
if (entry == null)
return null;
if (entry.cancel())
- scheduleEntry(entries.first());
+ scheduleEntry(entries.getNextToExpire());
return entry.getValue();
} finally {
writeLock.unlock();
@@ -1023,7 +924,7 @@ public boolean remove(Object key, Object value) {
if (entry != null && entry.getValue().equals(value)) {
entries.remove(key);
if (entry.cancel())
- scheduleEntry(entries.first());
+ scheduleEntry(entries.getNextToExpire());
return true;
} else
return false;
@@ -1038,7 +939,7 @@ public V replace(K key, V value) {
writeLock.lock();
try {
if (entries.containsKey(key)) {
- return putInternal(key, value, expirationPolicy.get(), expirationNanos.get());
+ return putInternal(key, value, expirationPolicy.get(), defaultExpirationDurationNanos.get());
} else
return null;
} finally {
@@ -1053,7 +954,7 @@ public boolean replace(K key, V oldValue, V newValue) {
try {
ExpiringEntry entry = entries.get(key);
if (entry != null && entry.getValue().equals(oldValue)) {
- putInternal(key, newValue, expirationPolicy.get(), expirationNanos.get());
+ putInternal(key, newValue, expirationPolicy.get(), defaultExpirationDurationNanos.get());
return true;
} else
return false;
@@ -1144,7 +1045,7 @@ public void setExpiration(K key, long duration, TimeUnit timeUnit) {
public void setExpiration(long duration, TimeUnit timeUnit) {
Assert.notNull(timeUnit, "timeUnit");
Assert.operation(variableExpiration, "Variable expiration is not enabled");
- expirationNanos.set(TimeUnit.NANOSECONDS.convert(duration, timeUnit));
+ defaultExpirationDurationNanos.set(TimeUnit.NANOSECONDS.convert(duration, timeUnit));
}
/**
@@ -1187,24 +1088,22 @@ public void setMaxSize(int maxSize) {
}
@Override
- public int size() {
- readLock.lock();
- try {
- return entries.size();
- } finally {
- readLock.unlock();
- }
- }
-
- @Override
- public String toString() {
- readLock.lock();
- try {
- return entries.toString();
- } finally {
- readLock.unlock();
- }
- }
+ public int size() {
+ return withReadLock(new LockOperation() {
+ public Integer get() {
+ return entries.size();
+ }
+ });
+ }
+
+ @Override
+ public String toString() {
+ return withReadLock(new LockOperation() {
+ public String get() {
+ return entries.toString();
+ }
+ });
+ }
/**
* Returns a copy of the map's values, which can be iterated over safely by multiple threads.
@@ -1272,52 +1171,53 @@ public void run() {
/**
* Returns the internal ExpiringEntry for the {@code key}, obtaining a read lock.
*/
- ExpiringEntry getEntry(Object key) {
- readLock.lock();
- try {
- return entries.get(key);
- } finally {
- readLock.unlock();
- }
- }
+ ExpiringEntry getEntry(final Object key) {
+ return withReadLock(new LockOperation>() {
+ public ExpiringEntry get() {
+ return entries.get(key);
+ }
+ });
+ }
/**
* Puts the given key/value in storage, scheduling the new entry for expiration if needed. If a previous value existed
* for the given key, it is first cancelled and the entries reordered to reflect the new expiration.
*/
- V putInternal(K key, V value, ExpirationPolicy expirationPolicy, long expirationNanos) {
- writeLock.lock();
- try {
- ExpiringEntry entry = entries.get(key);
- V oldValue = null;
-
- if (entry == null) {
- entry = new ExpiringEntry(key, value,
- variableExpiration ? new AtomicReference(expirationPolicy) : this.expirationPolicy,
- variableExpiration ? new AtomicLong(expirationNanos) : this.expirationNanos);
- if (entries.size() >= maxSize) {
- ExpiringEntry expiredEntry = entries.first();
- entries.remove(expiredEntry.key);
- notifyListeners(expiredEntry);
- }
- entries.put(key, entry);
- if (entries.size() == 1 || entries.first().equals(entry))
- scheduleEntry(entry);
- } else {
- oldValue = entry.getValue();
- if (!ExpirationPolicy.ACCESSED.equals(expirationPolicy)
- && ((oldValue == null && value == null) || (oldValue != null && oldValue.equals(value))))
- return value;
-
- entry.setValue(value);
- resetEntry(entry, false);
- }
-
- return oldValue;
- } finally {
- writeLock.unlock();
- }
- }
+ V putInternal(K key, V value, ExpirationPolicy expirationPolicy, long expirationNanos) {
+ ExpiringEntry entry = entries.get(key);
+ if (entry == null)
+ return createAndScheduleEntry(key, value, expirationPolicy, expirationNanos);
+
+ return updateExistingEntry(entry, value, expirationPolicy);
+ }
+
+ private V createAndScheduleEntry(K key, V value, ExpirationPolicy expirationPolicy, long expirationNanos) {
+ ExpiringEntry entry = new ExpiringEntry(key, value,
+ variableExpiration ? new AtomicReference(expirationPolicy) : this.expirationPolicy,
+ variableExpiration ? new AtomicLong(expirationNanos) : defaultExpirationDurationNanos);
+
+ if (entries.size() >= maxSize) {
+ ExpiringEntry expiredEntry = entries.getNextToExpire();
+ entries.remove(expiredEntry.key);
+ notifyListeners(expiredEntry);
+ }
+
+ entries.put(key, entry);
+ if (entries.size() == 1 || entries.getNextToExpire().equals(entry))
+ scheduleEntry(entry);
+ return null;
+ }
+
+ private V updateExistingEntry(ExpiringEntry entry, V value, ExpirationPolicy expirationPolicy) {
+ V oldValue = entry.getValue();
+ if (!ExpirationPolicy.ACCESSED.equals(expirationPolicy)
+ && ((oldValue == null && value == null) || (oldValue != null && oldValue.equals(value))))
+ return value;
+
+ entry.setValue(value);
+ resetEntry(entry, false);
+ return oldValue;
+ }
/**
* Resets the given entry's schedule canceling any existing scheduled expiration and reordering the entry in the
@@ -1331,10 +1231,10 @@ void resetEntry(ExpiringEntry entry, boolean scheduleFirstEntry) {
writeLock.lock();
try {
boolean scheduled = entry.cancel();
- entries.reorder(entry);
-
- if (scheduled || scheduleFirstEntry)
- scheduleEntry(entries.first());
+ entries.reorder(entry);
+
+ if (scheduled || scheduleFirstEntry)
+ scheduleEntry(entries.getNextToExpire());
} finally {
writeLock.unlock();
}
@@ -1397,7 +1297,7 @@ public void run() {
}
}
- private static Map.Entry mapEntryFor(final ExpiringEntry entry) {
+ private static Map.Entry mapEntryFor(final ExpiringEntry entry) {
return new Map.Entry() {
@Override
public K getKey() {
@@ -1414,9 +1314,40 @@ public V setValue(V value) {
throw new UnsupportedOperationException();
}
};
- }
-
- private void initListenerService() {
+ }
+
+ private T withReadLock(LockOperation operation) {
+ readLock.lock();
+ try {
+ return operation.get();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ private T withWriteLock(LockOperation operation) {
+ writeLock.lock();
+ try {
+ return operation.get();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ private void withWriteLock(Runnable operation) {
+ writeLock.lock();
+ try {
+ operation.run();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ private interface LockOperation {
+ T get();
+ }
+
+ private void initListenerService() {
synchronized (ExpiringMap.class) {
if (LISTENER_SERVICE == null) {
LISTENER_SERVICE = (ThreadPoolExecutor) Executors.newCachedThreadPool(
diff --git a/src/test/java/net/jodah/expiringmap/ExpiringEntryTest.java b/src/test/java/net/jodah/expiringmap/ExpiringEntryTest.java
index a22f8a4..58c2e61 100644
--- a/src/test/java/net/jodah/expiringmap/ExpiringEntryTest.java
+++ b/src/test/java/net/jodah/expiringmap/ExpiringEntryTest.java
@@ -11,8 +11,6 @@
import org.testng.annotations.Test;
-import net.jodah.expiringmap.ExpiringMap.ExpiringEntry;
-
/**
* Tests {@link ExpiringEntry}.
*/