3
3
import com .trivago .fastutilconcurrentwrapper .PrimitiveConcurrentMap ;
4
4
import com .trivago .fastutilconcurrentwrapper .PrimitiveKeyMap ;
5
5
import com .trivago .fastutilconcurrentwrapper .util .PaddedLock ;
6
+ import com .trivago .fastutilconcurrentwrapper .util .SmartIterator ;
7
+ import com .trivago .fastutilconcurrentwrapper .util .SmartLongIterator ;
6
8
import it .unimi .dsi .fastutil .longs .Long2ObjectMap ;
7
- import it .unimi .dsi .fastutil .longs .LongIterator ;
8
9
import it .unimi .dsi .fastutil .longs .LongSet ;
9
10
import it .unimi .dsi .fastutil .objects .ObjectCollection ;
11
+ import it .unimi .dsi .fastutil .objects .ObjectCollections ;
10
12
import it .unimi .dsi .fastutil .objects .ObjectSet ;
11
13
import org .jctools .maps .NonBlockingHashMapLong ;
12
14
import org .jspecify .annotations .Nullable ;
13
15
16
+ import java .util .Collection ;
14
17
import java .util .Map ;
15
18
import java .util .concurrent .CancellationException ;
16
19
import java .util .concurrent .ConcurrentMap ;
20
23
import java .util .function .Consumer ;
21
24
import java .util .function .Function ;
22
25
import java .util .function .LongConsumer ;
26
+ import java .util .function .Predicate ;
27
+ import java .util .stream .Stream ;
23
28
24
29
/**
25
30
Similar to {@link ConcurrentLongObjectMap}, but backed with NonBlockingHashMapLong ⇒ non-blocking reads 🚀
33
38
@see NBHMLCacheExpirer
34
39
*/
35
40
@ SuppressWarnings ("LockAcquiredButNotSafelyReleased" )
36
- public class StripedNonBlockingHashMapLong <E > implements ConcurrentMap <Long ,E >, Long2ObjectMap <E >, PrimitiveKeyMap {
41
+ public class StripedNonBlockingHashMapLong <E > implements ConcurrentMap <Long ,E >, Long2ObjectMap <E >, PrimitiveKeyMap , Iterable < Long > {
37
42
final NonBlockingHashMapLong <E > m ;
38
43
/** @see com.google.common.util.concurrent.Striped#lock(int) */
39
44
final PaddedLock [] s ;
@@ -174,7 +179,7 @@ public void putAll (Map<? extends Long,? extends E> fromMap) {
174
179
}
175
180
176
181
/** @see NonBlockingHashMapLong.IteratorLong */
177
- public static class StripedLongIterator implements LongIterator {
182
+ public static class StripedLongIterator implements SmartLongIterator {
178
183
private final StripedNonBlockingHashMapLong <?> owner ;
179
184
private final NonBlockingHashMapLong <?>.IteratorLong it ;
180
185
private long seenKey ;// ^ safe for concurrent
@@ -191,8 +196,6 @@ public void remove () {
191
196
it .remove ();
192
197
}
193
198
}
194
- /** <strong>Auto-box</strong> and return the next key. */
195
- @ Override @ Deprecated public Long next (){ return nextLong (); }
196
199
/** Return the next key as a primitive {@code long}. */
197
200
@ Override
198
201
public long nextLong () {
@@ -203,7 +206,7 @@ public long nextLong () {
203
206
@ Override public boolean hasNext (){ return it .hasNext (); }
204
207
}//StripedLongIterator
205
208
206
- public StripedLongIterator keys (){ return new StripedLongIterator (this ); }
209
+ @ Override public StripedLongIterator iterator (){ return new StripedLongIterator (this ); }
207
210
208
211
public void forEachKey (LongConsumer action ) {
209
212
var it = (NonBlockingHashMapLong <E >.IteratorLong ) m .keys ();
@@ -219,10 +222,110 @@ public LongSet keySet () {
219
222
}
220
223
public long [] keySetLong (){ return m .keySetLong (); }
221
224
222
- /** @see NonBlockingHashMapLong#values() */
225
+ /**
226
+ @see NonBlockingHashMapLong#values()
227
+ @see ObjectCollections#unmodifiable(ObjectCollection)
228
+ */
223
229
@ Override
224
230
public ObjectCollection <E > values () {
225
- throw new UnsupportedOperationException ();
231
+ var collection = m .values ();
232
+ return new ObjectCollection <>(){
233
+ @ Override
234
+ public boolean add (E k ) {
235
+ throw new UnsupportedOperationException ();
236
+ }
237
+ @ Override
238
+ public boolean remove (Object k ) {
239
+ throw new UnsupportedOperationException ();
240
+ }
241
+ @ Override
242
+ public int size () {
243
+ return collection .size ();
244
+ }
245
+ @ Override
246
+ public boolean isEmpty () {
247
+ return collection .isEmpty ();
248
+ }
249
+ @ Override
250
+ public boolean contains (Object o ) {
251
+ return collection .contains (o );
252
+ }
253
+
254
+ @ Override
255
+ public SmartIterator <E > iterator () {
256
+ var it = collection .iterator ();
257
+ return new SmartIterator <>(){
258
+ @ Override public boolean hasNext (){ return it .hasNext (); }
259
+ @ Override public E next (){ return it .next (); }
260
+ @ Override public String toString (){ return it .toString (); }
261
+ };
262
+ }
263
+
264
+ @ Override public SmartIterator <E > spliterator (){ return iterator (); }
265
+ @ Override public Stream <E > stream (){ return iterator ().stream (); }
266
+ @ Override public Stream <E > parallelStream (){ return iterator ().stream ().parallel (); }
267
+
268
+ @ Override
269
+ public void clear () {
270
+ throw new UnsupportedOperationException ();
271
+ }
272
+
273
+ @ Override
274
+ public <T > T [] toArray (T [] a ) {
275
+ return collection .toArray (a );
276
+ }
277
+
278
+ @ Override
279
+ public Object [] toArray () {
280
+ return collection .toArray ();
281
+ }
282
+
283
+ @ Override
284
+ public void forEach (final Consumer <? super E > action ) {
285
+ collection .forEach (action );
286
+ }
287
+
288
+ @ Override
289
+ public boolean containsAll (Collection <?> c ) {
290
+ return collection .containsAll (c );
291
+ }
292
+
293
+ @ Override
294
+ public boolean addAll (Collection <? extends E > c ) {
295
+ throw new UnsupportedOperationException ();
296
+ }
297
+
298
+ @ Override
299
+ public boolean removeAll (Collection <?> c ) {
300
+ throw new UnsupportedOperationException ();
301
+ }
302
+
303
+ @ Override
304
+ public boolean retainAll (Collection <?> c ) {
305
+ throw new UnsupportedOperationException ();
306
+ }
307
+
308
+ @ Override
309
+ public boolean removeIf (Predicate <? super E > filter ) {
310
+ throw new UnsupportedOperationException ();
311
+ }
312
+
313
+ @ Override
314
+ public String toString () {
315
+ return collection .toString ();
316
+ }
317
+
318
+ @ Override
319
+ public int hashCode () {
320
+ return collection .hashCode ();
321
+ }
322
+
323
+ @ Override
324
+ public boolean equals (Object o ) {
325
+ if (o == this ) return true ;
326
+ return collection .equals (o );
327
+ }
328
+ };
226
329
}
227
330
228
331
@ Override
@@ -304,8 +407,8 @@ public <R> R withLock (long key, Function<Long2ObjectMap.Entry<E>,R> withLock) {
304
407
}
305
408
}
306
409
307
-
308
- public abstract static class NBHMLCacheExpirer <E > implements LongConsumer , Function < Long2ObjectMap . Entry < E >, Object > {
410
+ /** Simple template to add an expiration support */
411
+ public abstract static class NBHMLCacheExpirer <E > {
309
412
protected final StripedNonBlockingHashMapLong <E > cacheMap ;
310
413
311
414
public NBHMLCacheExpirer (StripedNonBlockingHashMapLong <E > cacheMap ){ this .cacheMap = cacheMap ; }//new
@@ -333,35 +436,37 @@ protected void beforeExpire () {
333
436
334
437
@see org.springframework.scheduling.annotation.Scheduled
335
438
@see java.util.Set#removeIf
439
+
440
+ @see StripedNonBlockingHashMapLong#forEachKey(LongConsumer)
441
+ @see StripedNonBlockingHashMapLong#withLock
336
442
*/
337
- public void expire () {
443
+ public long expire () {
338
444
long initialExpiredCount = expiredCount ;
339
445
beforeExpire ();
340
- cacheMap .forEachKey (this );// see accept(long longKey)
341
- reportExpireResult (initialExpiredCount );
446
+ try {
447
+ for (var it = (NonBlockingHashMapLong <E >.IteratorLong ) cacheMap .m .keys (); it .hasNext ();){
448
+ long longKey = it .nextLong ();
449
+ E value = cacheMap .get (longKey );
450
+ if (value != null && isExpired (longKey , value )){// first "light" check: value could be gone already #1
451
+
452
+ try (var __ = cacheMap .write (longKey )){
453
+ value = cacheMap .get (longKey );// can be gone already #2
454
+ if (value != null && isExpired (longKey , value )){// double check idiom
455
+ expiredCount ++;
456
+ postProcessExpiredEntry (longKey , value );
457
+ it .remove ();// expired ⇒ remove: we inside the write lock ⇒ allowed
458
+ }
459
+ }
460
+
461
+ }//#1
462
+ }//f keys
463
+ } catch (CancellationException ignored ){}
464
+ return afterExpire (initialExpiredCount );
342
465
}
343
-
344
- /** @see #forEachKey(LongConsumer) */
345
- @ Override
346
- public void accept (long longKey ) {
347
- E value = cacheMap .get (longKey );// can be gone already
348
- if (value != null && isExpired (longKey , value ))// first "light" check
349
- cacheMap .withLock (longKey , this );
350
- }//LongConsumer#accept for expire → forEachKey
351
-
352
- /** @see #withLock(long, Function) */
353
- @ Override
354
- public @ Nullable Object apply (Long2ObjectMap .Entry <E > e ) {
355
- if (e .getValue () != null && isExpired (e .getLongKey (), e .getValue ())){// double check idiom
356
- expiredCount ++;
357
- postProcessExpiredEntry (e .getLongKey (), e .getValue ());
358
- e .setValue (null );// expired ⇒ remove
359
- }
360
- return null ;
361
- }//Function#apply for LongConsumer#accept → withLock
362
-
363
- private void reportExpireResult (long initialExpiredCount ) {
466
+ /** e.g. reportExpireResult */
467
+ protected long afterExpire (long initialExpiredCount ) {
364
468
//LOGGER.debug("{}.expire entries: {} / {}, expiredSinceStart: {}", beanName, expiredCount - initialExpiredCount, cacheMap.size(), expiredCount);
469
+ return expiredCount - initialExpiredCount ;
365
470
}
366
471
}
367
472
}
0 commit comments