BACKEND.*/JAVA

LinkedHashMap에서 accessOrder true일때 주의할 점

초코푸딩 2025. 1. 31. 21:45

Get 할 때도 lock 또는 syncronized 사용해야 한다! 이유는 아래에..

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;
    private final ReentrantLock lock = new ReentrantLock();

    public LRUCache(int maxSize) {
        super(maxSize, 0.75f, true); // accessOrder = true (최근 사용된 순서 유지)
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize; // 캐시 크기를 초과하면 가장 오래된 항목 제거
    }

    // Lock을 사용하여 동기화된 put()
    public void putSafe(K key, V value) {
        lock.lock();
        try {
            super.put(key, value);
        } finally {
            lock.unlock();
        }
    }

    // Lock을 사용하여 동기화된 get()
    public V getSafe(K key) {
        lock.lock();
        try {
            return super.get(key);
        } finally {
            lock.unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(2);

        Runnable task1 = () -> {
            for (int i = 0; i < 5; i++) {
                cache.putSafe(i, "Value" + i);
                System.out.println(Thread.currentThread().getName() + " PUT " + i);
            }
        };

        Runnable task2 = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " GET " + cache.getSafe(i));
            }
        };

        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);
        thread1.start();
        thread2.start();
    }
}

 

 

메서드 동기화 필요 여부 이유
put() ✅ 필요 내부적으로 연결 리스트를 변경하기 때문
get() (accessOrder = false) ❌ 필요 없음 단순 읽기 연산이므로 안전함
get() (accessOrder = true) ✅ 필요 recordAccess()를 호출하여 연결 리스트를 수정하기 때문

즉, 기본적으로 put()은 동기화해야 하지만, accessOrder = true인 경우에는 get()도 동기화하는 것이 안전함.

 

recordAccess() 메서드는 요소가 접근될 때마다(= get() 또는 put()이 호출될 때) 내부적인 연결 리스트를 조정하기 위해 사용되기 때문이다.

void recordAccess(Map<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>) m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove();
        addBefore(lm.head);
    }
}
  • accessOrder가 true인 경우(즉, LRU 캐시 모드인 경우), 최근 접근된 엔트리를 리스트의 마지막으로 이동합니다.
  • remove()를 통해 기존 위치에서 엔트리를 제거한 뒤,
  • addBefore()를 호출하여 새로운 위치에 삽입합니다.