[英]Is this Java cache threadsafe and optimal?
Can this Java cache be safer or faster? 这个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() { ... }
}
Yes. 是。 Even ignoring the fact that you haven't made lastupdateTime
or cachedData
volatile, 甚至忽略了你没有使lastupdateTime
或cachedData
volatile的事实,
lastupdateTime = now;
cachedData = getDataFromDatabase();
is out of order. 出了故障。
If getDataFromDatabase
fails with an exception, you will have already updated lastupdateTime
, so will keep returning stale data for 30s, possibly returning null
if getDataFromDatabase
fails on the first attempt. 如果getDataFromDatabase
因异常而失败,您将更新lastupdateTime
,因此将继续返回过时数据30秒,如果getDataFromDatabase
在第一次尝试失败时可能返回null
。
You would lose nothing by initializing lastUpdateTime
to Long.MIN_VALUE
, and would work more reliably on systems whose clocks have been set backwards. 通过将lastUpdateTime
初始化为Long.MIN_VALUE
,您将不会失去任何东西,并且可以在时钟向后设置的系统上更可靠地工作。
System.getCurrentTimeMillis
can go backwards which is unlikely to affect a 30s cache, but for this use case there's really no reason not to use System.nanoTime()
instead. System.getCurrentTimeMillis
可以向后移动 ,这不太可能影响30s缓存,但对于这个用例,实际上没有理由不使用System.nanoTime()
。
Here's an idea - on the principle that synchronized
is an archaic and inefficient mechanism. 这是一个想法 - 基于synchronized
是一种陈旧而低效的机制的原则。
Here I use an AtomicReference<Phaser>
to indicate the cache is being updated. 在这里,我使用AtomicReference<Phaser>
来指示正在更新缓存。 The Phaser
can be used to await completion of the update. 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();
}
}
Please note that this is the first time I have used a Phaser
- I hope I have it right. 请注意,这是我第一次使用Phaser
- 我希望我做对了。
Note that this algorithm may break down if the timeout is longer than the time it takes to get the data from the database. 请注意,如果超时超过从数据库获取数据所需的时间,则此算法可能会中断。
You have your Cash
class with two shared * mutable * state lastupdateTime
and cachedData
, getData()
can be invoked conurrenctly by two different threads, it use a local variable now
that can be visible to other threads (alocated in the stack
) 你的Cash
类有两个共享 * mutable *状态lastupdateTime
和cachedData
, getData()
可以由两个不同的线程同时调用,它now
使用一个局部变量,对其他线程可见(在stack
备份)
First if (now - lastupdateTime < 30000) { return cachedData;}
, Other thread can see stale
data on lastupdateTime
and cachedData
(you are accessing to a thared mutable state without synchronization guarantee) 首先是if (now - lastupdateTime < 30000) { return cachedData;}
,其他线程可以看到lastupdateTime
和cachedData
上的stale
数据(你正在访问一个没有同步保证的可变状态)
now
variable form such a invariant with lastupdateTime
, so now - lastupdateTime
should be executed in an Atomic Block
now
变量与lastupdateTime
形成这样的不变量 ,所以now - lastupdateTime
应该在Atomic Block
执行
Just make method
synchronized
, and make your class totally thread-safe 只需使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() { ... }
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.