/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cinchapi.concourse.server.storage.cache;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.Nullable;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* A {@link LazyCache} is a simple and eventually consistent cache:
* {@link #put(Object, Object) Put} items into the collection and
* eventually a call to {@link #get(Object)} will reflect the update.
* <p>
* The primary purpose of a {@link LazyCache} is to accommodate situations where
* caching helps performance, but retrieving cached data is not critical for
* correctness (i.e. worst case having duplicate instances of data won't lead to
* bad results). In particular this is designed to avoid overhead with placing
* new items into the cache when there is a cache miss.
* </p>
* <p>
* When placing items into the cache, the method returns immediately and the
* cache addition is scheduled to run in the background at some later time. So
* this cache only guarantees that it will eventually make updates and calls to
* retrieve items may report stale data for some time. Therefore, this cache is
* not a good candidate for data that changes frequently. It's best used as a
* sort of intern pool for immutable objects.
* </p>
*
* @author Jeff Nelson
*/
public class LazyCache<K, V> {
/**
* Return a {@link LazyCache} that is initially sized to accommodate the
* specified number of elements.
*
* @param size
* @return the LazyCache
*/
public static <K, V> LazyCache<K, V> withExpectedSize(int size) {
return new LazyCache<K, V>(Maps.<K, V> newHashMapWithExpectedSize(size));
}
/**
* The service that handles placing items into the cache.
*/
private final ExecutorService executor;
/**
* The internal data structure the holds the cached content.
*/
private final Map<K, V> internal;
/**
* Construct a new instance
*
* @param initialSize
*/
private LazyCache(Map<K, V> internal) {
this.internal = internal;
executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("lazy-cache-" + System.identityHashCode(this))
.build());
}
/**
* Return the value, if any, that is associated with the {@code key} in this
* cache. Recent updates to the cache are not guaranteed to be reflected.
*
* @param key
* @return the value associated with {@code key} at the time of invocation,
* or {@code null} if no such value exists
*/
@Nullable
public V get(K key) {
return internal.get(key);
}
/**
* Associate {@code key} with {@code value} in the cache. This update will
* eventually take effect.
*
* @param key
* @param value
*/
public void put(K key, V value) {
executor.execute(new PutRunnable(key, value));
}
/**
* The {@link Runnable} that is passed to the {@link #executor} to add items
* to the cache.
*
* @author Jeff Nelson
*/
private final class PutRunnable implements Runnable {
private final K key;
private final V value;
/**
* Construct a new instance.
*
* @param key
* @param value
*/
public PutRunnable(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public void run() {
internal.put(key, value);
}
}
}