ここにアイデアがあります -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
-うまくいけばいいのですが。
タイムアウトがデータベースからデータを取得するのにかかる時間よりも長い場合、このアルゴリズムが機能しなくなる可能性があることに注意してください。