org.apache.dubbo.cache.support.expiring.ExpiringMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dubbo Show documentation
Show all versions of dubbo Show documentation
The all in one project of dubbo
/*
* 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.
*/
package org.apache.dubbo.cache.support.expiring;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* can be expired map
* Contains a background thread that periodically checks if the data is out of date
*/
public class ExpiringMap implements Map {
/**
* default time to live (second)
*/
private static final int DEFAULT_TIME_TO_LIVE = 180;
/**
* default expire check interval (second)
*/
private static final int DEFAULT_EXPIRATION_INTERVAL = 1;
private static AtomicInteger expireCount = new AtomicInteger(1);
private final ConcurrentHashMap delegateMap;
private final ExpireThread expireThread;
public ExpiringMap() {
this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
}
/**
* Constructor
*
* @param timeToLive time to live (second)
*/
public ExpiringMap(int timeToLive) {
this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
}
public ExpiringMap(int timeToLive, int expirationInterval) {
this(new ConcurrentHashMap<>(), timeToLive, expirationInterval);
}
private ExpiringMap(ConcurrentHashMap delegateMap, int timeToLive, int expirationInterval) {
this.delegateMap = delegateMap;
this.expireThread = new ExpireThread();
expireThread.setTimeToLive(timeToLive);
expireThread.setExpirationInterval(expirationInterval);
}
@Override
public V put(K key, V value) {
ExpiryObject answer = delegateMap.put(key, new ExpiryObject(key, value, System.currentTimeMillis()));
if (answer == null) {
return null;
}
return answer.getValue();
}
@Override
public V get(Object key) {
ExpiryObject object = delegateMap.get(key);
if (object != null) {
long timeIdle = System.currentTimeMillis() - object.getLastAccessTime();
int timeToLive = expireThread.getTimeToLive();
if (timeToLive > 0 && timeIdle >= timeToLive * 1000) {
delegateMap.remove(object.getKey());
return null;
}
object.setLastAccessTime(System.currentTimeMillis());
return object.getValue();
}
return null;
}
@Override
public V remove(Object key) {
ExpiryObject answer = delegateMap.remove(key);
if (answer == null) {
return null;
}
return answer.getValue();
}
@Override
public boolean containsKey(Object key) {
return delegateMap.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegateMap.containsValue(value);
}
@Override
public int size() {
return delegateMap.size();
}
@Override
public boolean isEmpty() {
return delegateMap.isEmpty();
}
@Override
public void clear() {
delegateMap.clear();
expireThread.stopExpiring();
}
@Override
public int hashCode() {
return delegateMap.hashCode();
}
@Override
public Set keySet() {
return delegateMap.keySet();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return delegateMap.equals(obj);
}
@Override
public void putAll(Map inMap) {
for (Entry e : inMap.entrySet()) {
this.put(e.getKey(), e.getValue());
}
}
@Override
public Collection values() {
List list = new ArrayList();
Set> delegatedSet = delegateMap.entrySet();
for (Entry entry : delegatedSet) {
ExpiryObject value = entry.getValue();
list.add(value.getValue());
}
return list;
}
@Override
public Set> entrySet() {
throw new UnsupportedOperationException();
}
public ExpireThread getExpireThread() {
return expireThread;
}
public int getExpirationInterval() {
return expireThread.getExpirationInterval();
}
public void setExpirationInterval(int expirationInterval) {
expireThread.setExpirationInterval(expirationInterval);
}
public int getTimeToLive() {
return expireThread.getTimeToLive();
}
public void setTimeToLive(int timeToLive) {
expireThread.setTimeToLive(timeToLive);
}
@Override
public String toString() {
return "ExpiringMap{" +
"delegateMap=" + delegateMap.toString() +
", expireThread=" + expireThread.toString() +
'}';
}
/**
* can be expired object
*/
private class ExpiryObject {
private K key;
private V value;
private AtomicLong lastAccessTime;
ExpiryObject(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 = new AtomicLong(lastAccessTime);
}
public long getLastAccessTime() {
return lastAccessTime.get();
}
public void setLastAccessTime(long lastAccessTime) {
this.lastAccessTime.set(lastAccessTime);
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return value.equals(obj);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return "ExpiryObject{" +
"key=" + key +
", value=" + value +
", lastAccessTime=" + lastAccessTime +
'}';
}
}
/**
* Background thread, periodically checking if the data is out of date
*/
public class ExpireThread implements Runnable {
private long timeToLiveMillis;
private long expirationIntervalMillis;
private volatile boolean running = false;
private final Thread expirerThread;
@Override
public String toString() {
return "ExpireThread{" +
", timeToLiveMillis=" + timeToLiveMillis +
", expirationIntervalMillis=" + expirationIntervalMillis +
", running=" + running +
", expirerThread=" + expirerThread +
'}';
}
public ExpireThread() {
expirerThread = new Thread(this, "ExpiryMapExpire-" + expireCount.getAndIncrement());
expirerThread.setDaemon(true);
}
@Override
public void run() {
while (running) {
processExpires();
try {
Thread.sleep(expirationIntervalMillis);
} catch (InterruptedException e) {
running = false;
}
}
}
private void processExpires() {
long timeNow = System.currentTimeMillis();
if (timeToLiveMillis <= 0) {
return;
}
for (ExpiryObject o : delegateMap.values()) {
long timeIdle = timeNow - o.getLastAccessTime();
if (timeIdle >= timeToLiveMillis) {
delegateMap.remove(o.getKey());
}
}
}
/**
* start expiring Thread
*/
public void startExpiring() {
if (!running) {
running = true;
expirerThread.start();
}
}
/**
* start thread
*/
public void startExpiryIfNotStarted() {
if (running && timeToLiveMillis <= 0) {
return;
}
startExpiring();
}
/**
* stop thread
*/
public void stopExpiring() {
if (running) {
running = false;
expirerThread.interrupt();
}
}
/**
* get thread state
*
* @return thread state
*/
public boolean isRunning() {
return running;
}
/**
* get time to live
*
* @return time to live
*/
public int getTimeToLive() {
return (int) timeToLiveMillis / 1000;
}
/**
* update time to live
*
* @param timeToLive time to live
*/
public void setTimeToLive(long timeToLive) {
this.timeToLiveMillis = timeToLive * 1000;
}
/**
* get expiration interval
*
* @return expiration interval (second)
*/
public int getExpirationInterval() {
return (int) expirationIntervalMillis / 1000;
}
/**
* set expiration interval
*
* @param expirationInterval expiration interval (second)
*/
public void setExpirationInterval(long expirationInterval) {
this.expirationIntervalMillis = expirationInterval * 1000;
}
}
}