这个Java缓存可以更安全或更快吗?
public class Cache {
private long lastupdateTime = 0L;
private String cachedData = null;
public String getData() {
long now = System.currentTimeMillis();
if (now - lastupdateTime < 30000) {
return cachedData;
}
else {
synchronized (this) {
if (now - lastupdateTime < 30000) {
return cachedData;
}
else {
lastupdateTime = now;
cachedData = getDataFromDatabase();
return cachedData;
}
}
}
}
private String getDataFromDatabase() { ... }
}
答案 0 :(得分:2)
是。甚至忽略了你没有lastupdateTime
或cachedData
易变的事实,
lastupdateTime = now;
cachedData = getDataFromDatabase();
出了故障。
如果getDataFromDatabase
因异常而失败,您将更新lastupdateTime
,因此将继续返回30秒的陈旧数据,如果null
失败,可能会返回getDataFromDatabase
第一次尝试。
通过将lastUpdateTime
初始化为Long.MIN_VALUE
,您将不会失去任何东西,并且会在时钟设置为倒退的系统上更可靠地工作。
System.getCurrentTimeMillis
可以转到backwards这不太可能影响30s缓存,但对于这个用例,实际上没有理由不使用System.nanoTime()
。
答案 1 :(得分:1)
这是一个想法 - 基于synchronized
是一种陈旧而低效的机制的原则。
这里我使用AtomicReference<Phaser>
来表示正在更新缓存。 Phaser
可用于等待更新完成。
public class Test {
public static class Cache {
// Cache timeout.
private static final long Timeout = 10000;
// Last time we updated.
private volatile long lastupdateTime = 0L;
// The cached data.
private volatile String cachedData = null;
// Cache is in the progress of updating.
private AtomicReference<Phaser> updating = new AtomicReference<>();
// The next Phaser to use - avoids unnecessary Phaser creation.
private Phaser nextPhaser = new Phaser(1);
public String getData() {
// Do this just once.
long now = System.currentTimeMillis();
// Watch for expiry.
if (now - lastupdateTime > Timeout) {
// Make sure only one thread updates.
if (updating.compareAndSet(null, nextPhaser)) {
// We are the unique thread that gets to do the updating.
System.out.println(Thread.currentThread().getName() + " - Get ...");
// Get my new cache data.
cachedData = getDataFromDatabase();
lastupdateTime = now;
// Make the Phaser to use next time - avoids unnecessary creations.
nextPhaser = new Phaser(1);
// Get the queue and clear it - there cannot be any new joiners after this.
Phaser queue = updating.getAndSet(null);
// Inform everyone who is waiting that they can go.
queue.arriveAndDeregister();
} else {
// Wait in the queue.
Phaser queue = updating.get();
if (queue != null) {
// Wait for it.
queue.register();
System.out.println(Thread.currentThread().getName() + " - Waiting ...");
queue.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName() + " - Back");
}
}
}
// Let them have the correct data.
return cachedData;
}
private String getDataFromDatabase() {
try {
// Deliberately wait for a bit.
Thread.sleep(5000);
} catch (InterruptedException ex) {
// Ignore.
}
System.out.println(Thread.currentThread().getName() + " - Hello");
return "Hello";
}
}
public void test() throws InterruptedException {
System.out.println("Hello");
// Start time.
final long start = System.currentTimeMillis();
// Make a Cache.
final Cache c = new Cache();
// Make some threads.
for (int i = 0; i < 20; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (System.currentTimeMillis() - start < 60000) {
c.getData();
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
// Ignore.
}
}
}
});
t.setName("Thread - " + i);
t.start();
// Stagger the threads.
Thread.sleep(300);
}
}
public static void main(String args[]) throws InterruptedException {
new Test().test();
}
}
请注意,这是我第一次使用Phaser
- 我希望我做对了。
请注意,如果超时超过从数据库获取数据所需的时间,则此算法可能会中断。
答案 2 :(得分:0)
您的Cash
课程包含两个共享 * 可变 *州lastupdateTime
和cachedData
,getData()
可以由两个不同的线程同时调用,它使用一个局部变量now
,其他线程可以看到(stack
中的#{}}
首先if (now - lastupdateTime < 30000) { return cachedData;}
,其他主题可以在stale
和lastupdateTime
上看到cachedData
数据(您正在访问一个没有同步<的可变状态< / strong>保证)
now
变量使用lastupdateTime
形成不变,因此now - lastupdateTime
应在Atomic Block
只需制作method
synchronized
,让您的课程完全线程安全
public class Cache {
private long lastupdateTime = 0L;
private String cachedData = null;
public synchronized String getData() {
long now = System.currentTimeMillis()
if (nano - lastupdateTime < 30000) {
return cachedData;
}
else {
lastupdateTime = now;
cachedData = getDataFromDatabase();
return cachedData;
}
}
private String getDataFromDatabase() { ... }
}