
org.osgl.cache.impl.SimpleCacheService Maven / Gradle / Ivy
/*
* Copyright (C) 2013 The Rythm Engine project
* Gelin Luo
*
* 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.osgl.cache.impl;
/*-
* #%L
* OSGL Cache API
* %%
* Copyright (C) 2017 OSGL (Open Source General Library)
* %%
* 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.
* #L%
*/
import org.osgl.$;
import org.osgl.logging.L;
import org.osgl.logging.Logger;
import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.WeakHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A simple cache service implementation based on concurrent hash map
*/
public class SimpleCacheService extends CacheServiceBase {
private String name;
private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = _lock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = _lock.writeLock();
private static final Logger logger = L.get(SimpleCacheService.class);
private static class TimerThreadFactory implements ThreadFactory {
private String name;
TimerThreadFactory(String name) {
this.name = name;
}
private static final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
SecurityManager s = System.getSecurityManager();
ThreadGroup group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
Thread t = new Thread(group, r, "simple-cache-service-" + threadNumber.getAndIncrement(), 0);
t.setDaemon(true);
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
private ScheduledExecutorService scheduler = null;
SimpleCacheService(String name) {
this.name = name;
startup();
}
private static class Item implements Comparable- {
String key;
Object value;
long ts;
int ttl;
Item(String key, Object value, int ttl) {
this.key = key;
this.value = value;
this.ttl = ttl;
this.ts = $.ms();
}
@Override
public int compareTo(Item that) {
if (null == that) {
return 1;
}
long myTs = ts + ttl * 1000;
long hisTs = that.ts + that.ttl * 1000;
return myTs < hisTs ? -1 : myTs > hisTs ? 1 : key.compareTo(that.key);
}
}
private static class SoftItem extends SoftReference
- implements Comparable
{
public SoftItem(String key, Object value, int ttl) {
super(new Item(key, value, ttl));
}
@Override
public int compareTo(SoftItem that) {
if (null == that) {
return 1;
}
Item myItem = get();
if (null == myItem) {
return -1;
}
Item hisItem = that.get();
return myItem.compareTo(hisItem);
}
}
private Map cache_ = new WeakHashMap<>();
private Queue items_ = new PriorityQueue<>();
@Override
public void put(String key, Object value, int ttl) {
if (null == key) throw new NullPointerException();
if (0 >= ttl) {
ttl = defaultTTL;
}
writeLock.lock();
try {
SoftItem item = cache_.get(key);
Item item1 = null == item ? null : item.get();
if (null == item1) {
SoftItem newItem = new SoftItem(key, value, ttl);
cache_.put(key, newItem);
items_.offer(newItem);
} else {
item1.value = value;
item1.ttl = ttl;
item1.ts = $.ms();
// so that we can re-position the item in the queue
items_.remove(item);
items_.offer(item);
}
} finally {
writeLock.unlock();
}
}
@Override
public T get(String key) {
readLock.lock();
try {
SoftItem item = cache_.get(key);
if (null == item) {
return null;
}
Item item1 = item.get();
return null == item1 ? null : (T) item1.value;
} finally {
readLock.unlock();
}
}
@Override
public void put(String key, Object value) {
put(key, value, defaultTTL);
}
@Override
public void evict(String key) {
writeLock.lock();
try {
cache_.remove(key);
} finally {
writeLock.unlock();
}
}
@Override
public void clear() {
writeLock.lock();
try {
cache_.clear();
items_.clear();
} finally {
writeLock.unlock();
}
}
private int defaultTTL = 60;
@Override
public void setDefaultTTL(int ttl) {
if (ttl == 0) throw new IllegalArgumentException("time to live value couldn't be zero");
this.defaultTTL = ttl;
}
@Override
public synchronized void shutdown() {
clear();
if (null != scheduler) {
scheduler.shutdown();
scheduler = null;
}
}
@Override
public synchronized void startup() {
if (null == scheduler) {
scheduler = new ScheduledThreadPoolExecutor(1, new TimerThreadFactory(name));
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (items_.isEmpty()) {
return;
}
boolean trace = logger.isTraceEnabled();
long now = System.currentTimeMillis();
if (trace) {
logger.trace(">>>>now:%s", now);
}
writeLock.lock();
try {
while (true) {
SoftItem item0 = items_.peek();
if (null == item0) {
break;
}
Item item = item0.get();
if (null == item) {
// garbage collected ?
items_.poll();
continue;
}
long ts = item.ts + item.ttl * 1000;
if ((ts) < now + 50) {
cache_.remove(item.key);
items_.poll();
if (trace) {
logger.trace("- %s at %s", item.key, ts);
}
continue;
} else {
if (!cache_.containsKey(item.key)) {
// evicted or garbage collected
items_.poll();
if (trace) {
logger.trace("cached item evicted: %s", item.key);
}
continue;
}
if (trace) {
logger.trace(">>>>ts: %s", ts);
}
}
break;
}
} finally {
writeLock.unlock();
}
}
}, 0, 100, TimeUnit.MILLISECONDS);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
shutdown();
}
});
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy