Assume that I have a fetcher that fetches an image from a given link on a separate thread. The image will then be cached in memory. Once the image already gets cached, the f
This can be accomplished via ConcurrentMap and AsyncSubject:
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.concurrent.*;
import javax.imageio.ImageIO;
import rx.*;
import rx.Scheduler.Worker;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
public class ObservableImageCache {
final ConcurrentMap> image =
new ConcurrentHashMap<>();
public Observable get(String url) {
AsyncSubject result = image.get(url);
if (result == null) {
result = AsyncSubject.create();
AsyncSubject existing = image.putIfAbsent(url, result);
if (existing == null) {
System.out.println("Debug: Downloading " + url);
AsyncSubject a = result;
Worker w = Schedulers.io().createWorker();
w.schedule(() -> {
try {
Thread.sleep(500); // for demo
URL u = new URL(url);
try (InputStream openStream = u.openStream()) {
a.onNext(ImageIO.read(openStream));
}
a.onCompleted();
} catch (IOException | InterruptedException ex) {
a.onError(ex);
} finally {
w.unsubscribe();
}
});
} else {
result = existing;
}
}
return result;
}
public static void main(String[] args) throws Exception {
ObservableImageCache cache = new ObservableImageCache();
CountDownLatch cdl = new CountDownLatch(4);
Observable img1 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/create.png");
System.out.println("Subscribing for IMG1");
img1.subscribe(e -> System.out.println("IMG1: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
Thread.sleep(500);
Observable img2 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/create.png");
System.out.println("Subscribing for IMG2");
img2.subscribe(e -> System.out.println("IMG2: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
Observable img3 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/amb.png");
Observable img4 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/amb.png");
Thread.sleep(500);
System.out.println("Subscribing for IMG3");
img3.subscribe(e -> System.out.println("IMG3: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
Thread.sleep(1000);
System.out.println("-> Should be immediate: ");
System.out.println("Subscribing for IMG4");
img4.subscribe(e -> System.out.println("IMG4: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
cdl.await();
}
}
I'm using the ConcurrentMap's putIfAbsent to make sure only one download is triggered for a new url; everyone else will receive the same AsyncSubject on which they can 'wait' and get the data once available and immediately after that. Usually, you'd want to limit the number of concurrent downloads by using a custom Scheduler.