/* * Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * 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.amazon.carbonado.util; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * A concurrent pool of weakly referenced values mapped by key. Values are * created (and recreated) as needed. * * @author Brian S O'Neill * @see AbstractPool * @since 1.2 */ abstract class AbstractWeakPool<K, V, E extends Exception> { private final ConcurrentMap<K, ValueRef<K, V>> mValues; private final ReferenceQueue<V> mValueRefQueue; protected AbstractWeakPool() { mValues = new ConcurrentHashMap<K, ValueRef<K, V>>(); mValueRefQueue = new ReferenceQueue<V>(); } /** * Returns a value for the given key. Unused values are automatically * cleared to free up memory, even if they are still held. Repeat calls are * guaranteed to return the same value instance only if the value is * strongly reachable. The following idiom should be used when using the * pool for maintaining locks: * * <pre> * // Store lock in local variable to be strongly reachable. * Lock lock = lockPool.get(key); * lock.lock(); * try { * // access the resource protected by this lock * ... * } finally { * lock.unlock(); * } * </pre> */ public V get(K key) throws E { clean(); ValueRef<K, V> valueRef = mValues.get(key); V value; if (valueRef == null || (value = valueRef.get()) == null) { try { value = create(key); } catch (Exception e) { // Workaround compiler bug. org.cojen.util.ThrowUnchecked.fire(e); return null; } valueRef = new ValueRef<K, V>(value, mValueRefQueue, key); while (true) { ValueRef<K, V> existingRef = mValues.putIfAbsent(key, valueRef); if (existingRef == null) { // Newly created value is now the official value. break; } V existing = existingRef.get(); if (existing != null) { // Someone else just created value before us. Use that // instead and chuck the new value object. value = existing; valueRef.clear(); break; } // Reference just got cleared. Try again. Explicitly remove it // to prevent an infinite loop. Note that the two argument // remove method is called to ensure that what is being removed // is not a new value. mValues.remove(((ValueRef<K, V>) existingRef).mKey, existingRef); } } return value; } /** * Manually remove a value, returning the old value. */ public V remove(Object key) { clean(); ValueRef<K, V> valueRef = mValues.remove(key); V value; if (valueRef != null && (value = valueRef.get()) != null) { valueRef.clear(); return value; } return null; } /** * Return a new value instance. */ protected abstract V create(K key) throws E; private void clean() { // Clean out cleared values. Reference<? extends V> ref; while ((ref = mValueRefQueue.poll()) != null) { // Note that the two argument remove method is called to ensure // that what is being removed is not a new value. mValues.remove(((ValueRef<K, V>) ref).mKey, ref); } } private static class ValueRef<K, V> extends WeakReference<V> { final K mKey; ValueRef(V value, ReferenceQueue<V> queue, K key) { super(value, queue); mKey = key; } } }