1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.shared_orchestra.util;
20
21 import java.util.*;
22
23
24 /**
25 * A bi-level cache based on HashMap for caching objects with minimal sychronization
26 * overhead. The limitation is that <code>remove()</code> is very expensive.
27 * <p>
28 * Access to L1 map is not sychronized, to L2 map is synchronized. New values
29 * are first stored in L2. Once there have been more that a specified mumber of
30 * misses on L1, L1 and L2 maps are merged and the new map assigned to L1
31 * and L2 cleared.
32 * </p>
33 * <p>
34 * IMPORTANT:entrySet(), keySet(), and values() return unmodifiable snapshot collections.
35 * </p>
36 *
37 * @author Anton Koinov (latest modification by $Author: grantsmith $)
38 * @version $Revision: 472630 $ $Date: 2006-11-08 15:40:03 -0500 (Wed, 08 Nov 2006) $
39 */
40 public abstract class BiLevelCacheMap implements Map
41 {
42 //~ Instance fields ----------------------------------------------------------------------------
43
44 private static final int INITIAL_SIZE_L1 = 32;
45
46 /** To preinitialize <code>_cacheL1</code> with default values use an initialization block */
47 protected Map _cacheL1;
48
49 /** Must be final because it is used for synchronization */
50 private final Map _cacheL2;
51 private final int _mergeThreshold;
52 private int _missCount;
53
54 //~ Constructors -------------------------------------------------------------------------------
55
56 public BiLevelCacheMap(int mergeThreshold)
57 {
58 _cacheL1 = new HashMap(INITIAL_SIZE_L1);
59 _cacheL2 = new HashMap(HashMapUtils.calcCapacity(mergeThreshold));
60 _mergeThreshold = mergeThreshold;
61 }
62
63 //~ Methods ------------------------------------------------------------------------------------
64
65 public boolean isEmpty()
66 {
67 synchronized (_cacheL2) {
68 return _cacheL1.isEmpty() && _cacheL2.isEmpty();
69 }
70 }
71
72 public void clear()
73 {
74 synchronized (_cacheL2) {
75 _cacheL1 = new HashMap(); // dafault size
76 _cacheL2.clear();
77 }
78 }
79
80 public boolean containsKey(Object key)
81 {
82 synchronized (_cacheL2) {
83 return _cacheL1.containsKey(key) || _cacheL2.containsKey(key);
84 }
85 }
86
87 public boolean containsValue(Object value)
88 {
89 synchronized (_cacheL2) {
90 return _cacheL1.containsValue(value) || _cacheL2.containsValue(value);
91 }
92 }
93
94 public Set entrySet()
95 {
96 synchronized (_cacheL2)
97 {
98 mergeIfL2NotEmpty();
99 return Collections.unmodifiableSet(_cacheL1.entrySet());
100 }
101 }
102
103 public Object get(Object key)
104 {
105 Map cacheL1 = _cacheL1;
106 Object retval = cacheL1.get(key);
107 if (retval != null)
108 {
109 return retval;
110 }
111
112 synchronized (_cacheL2)
113 {
114 // Has another thread merged caches while we were waiting on the mutex? Then check L1 again
115 if (cacheL1 != _cacheL1)
116 {
117 if ((retval = _cacheL1.get(key)) != null)
118 {
119 // do not update miss count (it is not a miss anymore)
120 return retval;
121 }
122 }
123
124 if ((retval = _cacheL2.get(key)) == null)
125 {
126 retval = newInstance(key);
127 if (retval != null)
128 {
129 put(key, retval);
130 mergeIfNeeded();
131 }
132 }
133 else
134 {
135 mergeIfNeeded();
136 }
137 }
138
139 return retval;
140 }
141
142 public Set keySet()
143 {
144 synchronized (_cacheL2)
145 {
146 mergeIfL2NotEmpty();
147 return Collections.unmodifiableSet(_cacheL1.keySet());
148 }
149 }
150
151 /**
152 * If key is already in cacheL1, the new value will show with a delay,
153 * since merge L2->L1 may not happen immediately. To force the merge sooner,
154 * call <code>size()<code>.
155 */
156 public Object put(Object key, Object value)
157 {
158 synchronized (_cacheL2)
159 {
160 _cacheL2.put(key, value);
161
162 // not really a miss, but merge to avoid big increase in L2 size
163 // (it cannot be reallocated, it is final)
164 mergeIfNeeded();
165 }
166
167 return value;
168 }
169
170 public void putAll(Map map)
171 {
172 synchronized (_cacheL2)
173 {
174 mergeIfL2NotEmpty();
175
176 // sepatare merge to avoid increasing L2 size too much
177 // (it cannot be reallocated, it is final)
178 merge(map);
179 }
180 }
181
182 /** This operation is very expensive. A full copy of the Map is created */
183 public Object remove(Object key)
184 {
185 synchronized (_cacheL2)
186 {
187 if (!_cacheL1.containsKey(key) && !_cacheL2.containsKey(key))
188 {
189 // nothing to remove
190 return null;
191 }
192
193 Object retval;
194 Map newMap;
195 synchronized (_cacheL1)
196 {
197 // "dummy" synchronization to guarantee _cacheL1 will be assigned after fully initialized
198 // at least until JVM 1.5 where this should be guaranteed by the volatile keyword
199 newMap = HashMapUtils.merge(_cacheL1, _cacheL2);
200 retval = newMap.remove(key);
201 }
202
203 _cacheL1 = newMap;
204 _cacheL2.clear();
205 _missCount = 0;
206 return retval;
207 }
208 }
209
210 public int size()
211 {
212 // Note: cannot simply return L1.size + L2.size
213 // because there might be overlaping of keys
214 synchronized (_cacheL2)
215 {
216 mergeIfL2NotEmpty();
217 return _cacheL1.size();
218 }
219 }
220
221 public Collection values()
222 {
223 synchronized (_cacheL2)
224 {
225 mergeIfL2NotEmpty();
226 return Collections.unmodifiableCollection(_cacheL1.values());
227 }
228 }
229
230 private void mergeIfL2NotEmpty()
231 {
232 if (!_cacheL2.isEmpty())
233 {
234 merge(_cacheL2);
235 }
236 }
237
238 private void mergeIfNeeded()
239 {
240 if (++_missCount >= _mergeThreshold)
241 {
242 merge(_cacheL2);
243 }
244 }
245
246 private void merge(Map map)
247 {
248 Map newMap;
249 synchronized (_cacheL1)
250 {
251 // "dummy" synchronization to guarantee _cacheL1 will be assigned after fully initialized
252 // at least until JVM 1.5 where this should be guaranteed by the volatile keyword
253 // But is this enough (in our particular case) to resolve the issues with DCL?
254 newMap = HashMapUtils.merge(_cacheL1, map);
255 }
256 _cacheL1 = newMap;
257 _cacheL2.clear();
258 _missCount = 0;
259 }
260
261 /**
262 * Subclasses must implement to have automatic creation of new instances
263 * or alternatively can use <code>put<code> to add new items to the cache.<br>
264 *
265 * Implementing this method is prefered to guarantee that there will be only
266 * one instance per key ever created. Calling put() to add items in a multi-
267 * threaded situation will require external synchronization to prevent two
268 * instances for the same key, which defeats the purpose of this cache
269 * (put() is useful when initialization is done during startup and items
270 * are not added during execution or when (temporarily) having possibly two
271 * or more instances of the same key is not of concern).<br>
272 *
273 * @param key lookup key
274 * @return new instace for the requested key
275 */
276 protected abstract Object newInstance(Object key);
277 }