Skip to content

Commit adf022c

Browse files
authored
Use ConcurrentLinkedHashMap as backing for LRUMap (#3531)
1 parent e7ab455 commit adf022c

File tree

12 files changed

+2411
-70
lines changed

12 files changed

+2411
-70
lines changed

pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@
9595
<version>${version.powermock}</version>
9696
<scope>test</scope>
9797
</dependency>
98+
<dependency>
99+
<groupId>com.google.guava</groupId>
100+
<artifactId>guava-testlib</artifactId>
101+
<version>31.1-jre</version>
102+
<scope>test</scope>
103+
</dependency>
98104
<!-- For testing TestNoClassDefFoundDeserializer -->
99105
<dependency>
100106
<groupId>javax.measure</groupId>
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.fasterxml.jackson.databind.util;
22

3-
import java.io.*;
4-
import java.util.concurrent.ConcurrentHashMap;
3+
import com.fasterxml.jackson.databind.util.internal.PrivateMaxEntriesMap;
54

65
/**
76
* Helper for simple bounded maps used for reusing lookup values.
@@ -10,44 +9,36 @@
109
* on assumption that all use cases are for caching where persistence
1110
* does not make sense. The only thing serialized is the cache size of Map.
1211
*<p>
13-
* NOTE: since version 2.4.2, this is <b>NOT</b> an LRU-based at all; reason
14-
* being that it is not possible to use JDK components that do LRU _AND_ perform
15-
* well wrt synchronization on multi-core systems. So we choose efficient synchronization
16-
* over potentially more efficient handling of entries.
12+
* NOTE: since Jackson 2.14, the implementation evicts the least recently used
13+
* entry when max size is reached.
1714
*<p>
18-
* And yes, there are efficient LRU implementations such as
19-
* <a href="https://code.google.com/p/concurrentlinkedhashmap/">concurrentlinkedhashmap</a>;
20-
* but at this point we really try to keep external deps to minimum.
21-
* Plan from Jackson 2.12 is to focus more on pluggability as {@link LookupCache} and
22-
* let users, frameworks, provide their own cache implementations.
15+
* Since Jackson 2.12, there has been pluggable {@link LookupCache} interface which
16+
* allows users, frameworks, provide their own cache implementations.
2317
*/
2418
public class LRUMap<K,V>
2519
implements LookupCache<K,V>, // since 2.12
2620
java.io.Serializable
2721
{
28-
private static final long serialVersionUID = 1L;
22+
private static final long serialVersionUID = 2L;
2923

30-
protected final transient int _maxEntries;
24+
protected final int _initialEntries;
25+
protected final int _maxEntries;
26+
protected final transient PrivateMaxEntriesMap<K,V> _map;
3127

32-
protected final transient ConcurrentHashMap<K,V> _map;
33-
3428
public LRUMap(int initialEntries, int maxEntries)
3529
{
36-
// We'll use concurrency level of 4, seems reasonable
37-
_map = new ConcurrentHashMap<K,V>(initialEntries, 0.8f, 4);
30+
_initialEntries = initialEntries;
3831
_maxEntries = maxEntries;
32+
// We'll use concurrency level of 4, seems reasonable
33+
_map = new PrivateMaxEntriesMap.Builder<K, V>()
34+
.initialCapacity(initialEntries)
35+
.maximumCapacity(maxEntries)
36+
.concurrencyLevel(4)
37+
.build();
3938
}
4039

4140
@Override
4241
public V put(K key, V value) {
43-
if (_map.size() >= _maxEntries) {
44-
// double-locking, yes, but safe here; trying to avoid "clear storms"
45-
synchronized (this) {
46-
if (_map.size() >= _maxEntries) {
47-
clear();
48-
}
49-
}
50-
}
5142
return _map.put(key, value);
5243
}
5344

@@ -56,21 +47,12 @@ public V put(K key, V value) {
5647
*/
5748
@Override
5849
public V putIfAbsent(K key, V value) {
59-
// not 100% optimal semantically, but better from correctness (never exceeds
60-
// defined maximum) and close enough all in all:
61-
if (_map.size() >= _maxEntries) {
62-
synchronized (this) {
63-
if (_map.size() >= _maxEntries) {
64-
clear();
65-
}
66-
}
67-
}
6850
return _map.putIfAbsent(key, value);
6951
}
7052

7153
// NOTE: key is of type Object only to retain binary backwards-compatibility
7254
@Override
73-
public V get(Object key) { return _map.get(key); }
55+
public V get(Object key) { return _map.get(key); }
7456

7557
@Override
7658
public void clear() { _map.clear(); }
@@ -84,23 +66,7 @@ public V putIfAbsent(K key, V value) {
8466
/**********************************************************
8567
*/
8668

87-
/**
88-
* Ugly hack, to work through the requirement that _value is indeed final,
89-
* and that JDK serialization won't call ctor(s) if Serializable is implemented.
90-
*
91-
* @since 2.1
92-
*/
93-
protected transient int _jdkSerializeMaxEntries;
94-
95-
private void readObject(ObjectInputStream in) throws IOException {
96-
_jdkSerializeMaxEntries = in.readInt();
97-
}
98-
99-
private void writeObject(ObjectOutputStream out) throws IOException {
100-
out.writeInt(_jdkSerializeMaxEntries);
101-
}
102-
10369
protected Object readResolve() {
104-
return new LRUMap<Object,Object>(_jdkSerializeMaxEntries, _jdkSerializeMaxEntries);
70+
return new LRUMap<K,V>(_initialEntries, _maxEntries);
10571
}
10672
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2010 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.fasterxml.jackson.databind.util.internal;
17+
18+
/**
19+
* A listener registered for notification when an entry is evicted. An instance
20+
* may be called concurrently by multiple threads to process entries. An
21+
* implementation should avoid performing blocking calls or synchronizing on
22+
* shared resources.
23+
* <p>
24+
* The listener is invoked by {@link PrivateMaxEntriesMap} on a caller's
25+
* thread and will not block other threads from operating on the map. An
26+
* implementation should be aware that the caller's thread will not expect
27+
* long execution times or failures as a side effect of the listener being
28+
* notified. Execution safety and a fast turn around time can be achieved by
29+
* performing the operation asynchronously, such as by submitting a task to an
30+
* {@link java.util.concurrent.ExecutorService}.
31+
*
32+
* @author [email protected] (Ben Manes)
33+
* @see <a href="http://code.google.com/p/concurrentlinkedhashmap/">
34+
* http://code.google.com/p/concurrentlinkedhashmap/</a>
35+
*/
36+
interface EvictionListener<K, V> {
37+
38+
/**
39+
* A call-back notification that the entry was evicted.
40+
*
41+
* @param key the entry's key
42+
* @param value the entry's value
43+
*/
44+
void onEviction(K key, V value);
45+
}

0 commit comments

Comments
 (0)