Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * */ import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * A map with expiration. This class contains a worker thread that will * periodically check this class in order to determine if any objects * should be removed based on the provided time-to-live value. * * @author The Apache MINA Project (dev@mina.apache.org) * @version $Rev: 662890 $, $Date: 2008-06-03 23:14:21 +0200 (Tue, 03 Jun 2008) $ */ public class ExpiringMap<K, V> implements Map<K, V> { /** * The default value, 60 */ public static final int DEFAULT_TIME_TO_LIVE = 60; /** * The default value, 1 */ public static final int DEFAULT_EXPIRATION_INTERVAL = 1; private static volatile int expirerCount = 1; private final ConcurrentHashMap<K, ExpiringObject> delegate; private final CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners; private final Expirer expirer; /** * Creates a new instance of ExpiringMap using the default values * DEFAULT_TIME_TO_LIVE and DEFAULT_EXPIRATION_INTERVAL * */ public ExpiringMap() { this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL); } /** * Creates a new instance of ExpiringMap using the supplied * time-to-live value and the default value for DEFAULT_EXPIRATION_INTERVAL * * @param timeToLive * The time-to-live value (seconds) */ public ExpiringMap(int timeToLive) { this(timeToLive, DEFAULT_EXPIRATION_INTERVAL); } /** * Creates a new instance of ExpiringMap using the supplied values and * a {@link ConcurrentHashMap} for the internal data structure. * * @param timeToLive * The time-to-live value (seconds) * @param expirationInterval * The time between checks to see if a value should be removed (seconds) */ public ExpiringMap(int timeToLive, int expirationInterval) { this(new ConcurrentHashMap<K, ExpiringObject>(), new CopyOnWriteArrayList<ExpirationListener<V>>(), timeToLive, expirationInterval); } private ExpiringMap(ConcurrentHashMap<K, ExpiringObject> delegate, CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners, int timeToLive, int expirationInterval) { this.delegate = delegate; this.expirationListeners = expirationListeners; this.expirer = new Expirer(); expirer.setTimeToLive(timeToLive); expirer.setExpirationInterval(expirationInterval); } public V put(K key, V value) { ExpiringObject answer = delegate.put(key, new ExpiringObject(key, value, System.currentTimeMillis())); if (answer == null) { return null; } return answer.getValue(); } public V get(Object key) { ExpiringObject object = delegate.get(key); if (object != null) { object.setLastAccessTime(System.currentTimeMillis()); return object.getValue(); } return null; } public V remove(Object key) { ExpiringObject answer = delegate.remove(key); if (answer == null) { return null; } return answer.getValue(); } public boolean containsKey(Object key) { return delegate.containsKey(key); } public boolean containsValue(Object value) { return delegate.containsValue(value); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } public Set<K> keySet() { return delegate.keySet(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } public void putAll(Map<? extends K, ? extends V> inMap) { for (Entry<? extends K, ? extends V> e : inMap.entrySet()) { this.put(e.getKey(), e.getValue()); } } public Collection<V> values() { throw new UnsupportedOperationException(); } public Set<Map.Entry<K, V>> entrySet() { throw new UnsupportedOperationException(); } public void addExpirationListener(ExpirationListener<V> listener) { expirationListeners.add(listener); } public void removeExpirationListener(ExpirationListener<V> listener) { expirationListeners.remove(listener); } public Expirer getExpirer() { return expirer; } public int getExpirationInterval() { return expirer.getExpirationInterval(); } public int getTimeToLive() { return expirer.getTimeToLive(); } public void setExpirationInterval(int expirationInterval) { expirer.setExpirationInterval(expirationInterval); } public void setTimeToLive(int timeToLive) { expirer.setTimeToLive(timeToLive); } private class ExpiringObject { private K key; private V value; private long lastAccessTime; private final ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock(); ExpiringObject(K key, V value, long lastAccessTime) { if (value == null) { throw new IllegalArgumentException("An expiring object cannot be null."); } this.key = key; this.value = value; this.lastAccessTime = lastAccessTime; } public long getLastAccessTime() { lastAccessTimeLock.readLock().lock(); try { return lastAccessTime; } finally { lastAccessTimeLock.readLock().unlock(); } } public void setLastAccessTime(long lastAccessTime) { lastAccessTimeLock.writeLock().lock(); try { this.lastAccessTime = lastAccessTime; } finally { lastAccessTimeLock.writeLock().unlock(); } } public K getKey() { return key; } public V getValue() { return value; } @Override public boolean equals(Object obj) { return value.equals(obj); } @Override public int hashCode() { return value.hashCode(); } } /** * A Thread that monitors an {@link ExpiringMap} and will remove * elements that have passed the threshold. * */ public class Expirer implements Runnable { private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); private long timeToLiveMillis; private long expirationIntervalMillis; private boolean running = false; private final Thread expirerThread; /** * Creates a new instance of Expirer. * */ public Expirer() { expirerThread = new Thread(this, "ExpiringMapExpirer-" + expirerCount++); expirerThread.setDaemon(true); } public void run() { while (running) { processExpires(); try { Thread.sleep(expirationIntervalMillis); } catch (InterruptedException e) { } } } private void processExpires() { long timeNow = System.currentTimeMillis(); for (ExpiringObject o : delegate.values()) { if (timeToLiveMillis <= 0) { continue; } long timeIdle = timeNow - o.getLastAccessTime(); if (timeIdle >= timeToLiveMillis) { delegate.remove(o.getKey()); for (ExpirationListener<V> listener : expirationListeners) { listener.expired(o.getValue()); } } } } /** * Kick off this thread which will look for old objects and remove them. * */ public void startExpiring() { stateLock.writeLock().lock(); try { if (!running) { running = true; expirerThread.start(); } } finally { stateLock.writeLock().unlock(); } } /** * If this thread has not started, then start it. * Otherwise just return; */ public void startExpiringIfNotStarted() { stateLock.readLock().lock(); try { if (running) { return; } } finally { stateLock.readLock().unlock(); } stateLock.writeLock().lock(); try { if (!running) { running = true; expirerThread.start(); } } finally { stateLock.writeLock().unlock(); } } /** * Stop the thread from monitoring the map. */ public void stopExpiring() { stateLock.writeLock().lock(); try { if (running) { running = false; expirerThread.interrupt(); } } finally { stateLock.writeLock().unlock(); } } /** * Checks to see if the thread is running * * @return * If the thread is running, true. Otherwise false. */ public boolean isRunning() { stateLock.readLock().lock(); try { return running; } finally { stateLock.readLock().unlock(); } } /** * Returns the Time-to-live value. * * @return * The time-to-live (seconds) */ public int getTimeToLive() { stateLock.readLock().lock(); try { return (int) timeToLiveMillis / 1000; } finally { stateLock.readLock().unlock(); } } /** * Update the value for the time-to-live * * @param timeToLive * The time-to-live (seconds) */ public void setTimeToLive(long timeToLive) { stateLock.writeLock().lock(); try { this.timeToLiveMillis = timeToLive * 1000; } finally { stateLock.writeLock().unlock(); } } /** * Get the interval in which an object will live in the map before * it is removed. * * @return * The time in seconds. */ public int getExpirationInterval() { stateLock.readLock().lock(); try { return (int) expirationIntervalMillis / 1000; } finally { stateLock.readLock().unlock(); } } /** * Set the interval in which an object will live in the map before * it is removed. * * @param expirationInterval * The time in seconds */ public void setExpirationInterval(long expirationInterval) { stateLock.writeLock().lock(); try { this.expirationIntervalMillis = expirationInterval * 1000; } finally { stateLock.writeLock().unlock(); } } } } /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * */ /** * A listener for expired object events. * * @author The Apache MINA Project (dev@mina.apache.org) * @version $Rev: 589932 $, $Date: 2007-10-30 02:50:39 +0100 (Tue, 30 Oct 2007) $ * TODO Make this a inner interface of ExpiringMap */ interface ExpirationListener<E> { void expired(E expiredObject); }