/* * Copyright 2006-2010 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 { private final ConcurrentMap> mValues; private final ReferenceQueue mValueRefQueue; protected AbstractWeakPool() { mValues = new ConcurrentHashMap>(); mValueRefQueue = new ReferenceQueue(); } /** * 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: * *
     * // 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();
     * }
     * 
*/ public V get(K key) throws E { clean(); ValueRef 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(value, mValueRefQueue, key); while (true) { ValueRef 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) existingRef).mKey, existingRef); } } return value; } /** * Manually remove a value, returning the old value. */ public V remove(Object key) { clean(); ValueRef 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 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) ref).mKey, ref); } } private static class ValueRef extends WeakReference { final K mKey; ValueRef(V value, ReferenceQueue queue, K key) { super(value, queue); mKey = key; } } }