001 /*
002 * Copyright (C) 2011 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package com.google.common.cache;
018
019 import com.google.common.annotations.Beta;
020 import com.google.common.util.concurrent.UncheckedExecutionException;
021
022 import java.util.concurrent.ConcurrentMap;
023 import java.util.concurrent.ExecutionException;
024 import java.util.concurrent.atomic.AtomicLong;
025
026 /**
027 * This class provides a skeletal implementation of the {@code Cache} interface to minimize the
028 * effort required to implement this interface.
029 *
030 * <p>To implement a cache, the programmer needs only to extend this class and provide an
031 * implementation for the {@code get} method. This implementation throws an
032 * {@link UnsupportedOperationException} on calls to {@link #size}, {@link #invalidate},
033 * {@link #invalidateAll}, {@link #stats}, and {@link #asMap}. The methods
034 * {@link #getUnchecked} and {@link #apply} are implemented in terms of {@link #get}. The method
035 * {@link #cleanUp} is a no-op.
036 *
037 * @author Charles Fry
038 * @since 10.0
039 */
040 @Beta
041 public abstract class AbstractCache<K, V> implements Cache<K, V> {
042
043 /** Constructor for use by subclasses. */
044 protected AbstractCache() {}
045
046 @Override
047 public V getUnchecked(K key) {
048 try {
049 return get(key);
050 } catch (ExecutionException e) {
051 throw new UncheckedExecutionException(e.getCause());
052 }
053 }
054
055 @Override
056 public final V apply(K key) {
057 return getUnchecked(key);
058 }
059
060 @Override
061 public void cleanUp() {}
062
063 @Override
064 public long size() {
065 throw new UnsupportedOperationException();
066 }
067
068 @Override
069 public void invalidate(Object key) {
070 throw new UnsupportedOperationException();
071 }
072
073 @Override
074 public void invalidateAll() {
075 throw new UnsupportedOperationException();
076 }
077
078 @Override
079 public CacheStats stats() {
080 throw new UnsupportedOperationException();
081 }
082
083 @Override
084 public ConcurrentMap<K, V> asMap() {
085 throw new UnsupportedOperationException();
086 }
087
088 /**
089 * Accumulates statistics during the operation of a {@link Cache} for presentation by {@link
090 * Cache#stats}. This is solely intended for consumption by {@code Cache} implementors.
091 *
092 * @since 10.0
093 */
094 @Beta
095 public interface StatsCounter {
096 /**
097 * Records a single hit. This should be called when a cache request returns a cached value.
098 */
099 public void recordHit();
100
101 /**
102 * Records the successful load of a new entry. This should be called when a cache request
103 * causes an entry to be loaded, and the loading completes succesfully. In contrast to
104 * {@link #recordConcurrentMiss}, this method should only be called by the loading thread.
105 *
106 * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new
107 * value
108 */
109 public void recordLoadSuccess(long loadTime);
110
111 /**
112 * Records the failed load of a new entry. This should be called when a cache request causes
113 * an entry to be loaded, but an exception is thrown while loading the entry. In contrast to
114 * {@link #recordConcurrentMiss}, this method should only be called by the loading thread.
115 *
116 * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new
117 * value prior to an exception being thrown
118 */
119 public void recordLoadException(long loadTime);
120
121 /**
122 * Records a single concurrent miss. This should be called when a cache request returns a
123 * value which was loaded by a different thread. In contrast to {@link #recordLoadSuccess}
124 * and {@link #recordLoadException}, this method should never be called by the loading
125 * thread. Multiple concurrent calls to {@link Cache} lookup methods with the same key on an
126 * absent value should result in a single call to either {@code recordLoadSuccess} or
127 * {@code recordLoadException} and multiple calls to this method, despite all being served by
128 * the results of a single load operation.
129 */
130 public void recordConcurrentMiss();
131
132 /**
133 * Records the eviction of an entry from the cache. This should only been called when an entry
134 * is evicted due to the cache's eviction strategy, and not as a result of manual {@linkplain
135 * Cache#invalidate invalidations}.
136 */
137 public void recordEviction();
138
139 /**
140 * Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as
141 * it may be interleaved with update operations.
142 */
143 public CacheStats snapshot();
144 }
145
146 /**
147 * A thread-safe {@link StatsCounter} implementation for use by {@link Cache} implementors.
148 *
149 * @since 10.0
150 */
151 @Beta
152 public static class SimpleStatsCounter implements StatsCounter {
153 private final AtomicLong hitCount = new AtomicLong();
154 private final AtomicLong missCount = new AtomicLong();
155 private final AtomicLong loadSuccessCount = new AtomicLong();
156 private final AtomicLong loadExceptionCount = new AtomicLong();
157 private final AtomicLong totalLoadTime = new AtomicLong();
158 private final AtomicLong evictionCount = new AtomicLong();
159
160 @Override
161 public void recordHit() {
162 hitCount.incrementAndGet();
163 }
164
165 @Override
166 public void recordLoadSuccess(long loadTime) {
167 missCount.incrementAndGet();
168 loadSuccessCount.incrementAndGet();
169 totalLoadTime.addAndGet(loadTime);
170 }
171
172 @Override
173 public void recordLoadException(long loadTime) {
174 missCount.incrementAndGet();
175 loadExceptionCount.incrementAndGet();
176 totalLoadTime.addAndGet(loadTime);
177 }
178
179 @Override
180 public void recordConcurrentMiss() {
181 missCount.incrementAndGet();
182 }
183
184 @Override
185 public void recordEviction() {
186 evictionCount.incrementAndGet();
187 }
188
189 @Override
190 public CacheStats snapshot() {
191 return new CacheStats(
192 hitCount.get(),
193 missCount.get(),
194 loadSuccessCount.get(),
195 loadExceptionCount.get(),
196 totalLoadTime.get(),
197 evictionCount.get());
198 }
199
200 /**
201 * Increments all counters by the values in {@code other}.
202 */
203 public void incrementBy(StatsCounter other) {
204 CacheStats otherStats = other.snapshot();
205 hitCount.addAndGet(otherStats.hitCount());
206 missCount.addAndGet(otherStats.missCount());
207 loadSuccessCount.addAndGet(otherStats.loadSuccessCount());
208 loadExceptionCount.addAndGet(otherStats.loadExceptionCount());
209 totalLoadTime.addAndGet(otherStats.totalLoadTime());
210 evictionCount.addAndGet(otherStats.evictionCount());
211 }
212 }
213 }