LRU Cache
Created on: Oct 13, 2024
You are tasked with designing a thread-safe cache using Java. The cache should store key-value pairs, and it should have the following features:
- Multiple threads can read from the cache simultaneously.
- Only one thread should be allowed to write or update a value for a key at a time.
- If a key does not exist in the cache, the cache should load the value from an external source (e.g., a database or a file), but this operation should be performed by only one thread for each key, to avoid redundant loading by multiple threads.
- Explain how you would design the cache, and what synchronization mechanisms (e.g.,
synchronized,ReentrantLock,ReadWriteLock, etc.) you would use to handle these requirements. - (Optional): How would you handle the cache eviction policy, like LRU (Least Recently Used), if the cache has a size limit?
Solution
import java.util.*; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; interface ApiData<T> { public T getAPiData(); } class ExternalApi implements ApiData<String> { public String getAPiData() { try { Thread.sleep(1000); } catch (InterruptedException exception) { exception.printStackTrace(); } return "Dummy value"; } } class Cache<K, V> { private Map<K, V> dataMap; private ApiData<V> apiData; private final int MAX_KEYS; private final ReadWriteLock lock; public Cache(ApiData<V> apiData, int maxKeys) { this.MAX_KEYS = maxKeys; this.apiData = apiData; lock = new ReentrantReadWriteLock(); this.dataMap = new LinkedHashMap<>(MAX_KEYS, 0.75f, true){ @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > MAX_KEYS; } }; } public void put(K key, V value) { lock.writeLock().lock(); try { dataMap.put(key, value); } catch (Exception exception) { exception.printStackTrace(); throw new RuntimeException("Exception in adding/updating a cache"); } finally { lock.writeLock().unlock(); } } public V get(K key) { lock.readLock().lock(); V data; try { if (dataMap.containsKey(key)) { data = dataMap.get(key); return data; } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Exception in fetching a cache"); } finally { lock.readLock().unlock(); } lock.writeLock().lock(); try { data = apiData.getAPiData(); dataMap.put(key, data); return data; } catch (Exception exception) { exception.printStackTrace(); throw new RuntimeException("Exception in fetching and updating data"); } finally { lock.writeLock().unlock(); } } } public class LRU_Cache { public static void main(String[] args) { ApiData<String> apiData = new ExternalApi(); Cache<String, String> cache = new Cache<>(apiData,4); cache.put("A", "1"); cache.put("B", "some value"); cache.put("C", "some value 2"); cache.put("D", "some value 3"); cache.put("E", "some value 3"); cache.put("F", "some value 3"); cache.put("G", "some value 3"); String value = cache.get("G"); System.out.println(value); System.out.println(cache.get("G")); } }
