并发hashmap缓存中的空值

时间:2015-04-28 15:45:10

标签: java multithreading caching

我试图在java中实现并发缓存以供学习建议。

此代码负责garantee线程安全操作。因此,每当线程尝试获取值时,如果此值尚未缓存,则算法应从最后一个缓存的值计算它。

我的问题是我得到了应该已经缓存的空值。我使用信号量(虽然我也尝试使用ReentrantLock,所以我认为这不是问题),以确保线程安全访问HashMap。

请注意,我想将锁定区域限制为尽可能小。所以我不想同步整个方法或利用已经线程安全的ConcurrentMap。

这是一个完整的简单代码:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;

public class ConcurrentCache {

    private final Semaphore semaphore = new Semaphore(1);

    private final Map<Integer, Integer> cache;
    private int lastCachedNumber;

    public ConcurrentCache() {
        cache = new HashMap<Integer, Integer>();
        cache.put(0, 0);
        lastCachedNumber = 0;
    }

    public Integer fetchAndCache(int n) {

        //if it's already cached, supposedly i can access it in an unlocked way
        if (n <= lastCachedNumber)  
            return cache.get(n);

        lock();

        Integer number;
        if (n < lastCachedNumber) { // check it again. it may be updated by another thread
            number = cache.get(n);
        } else {
            //fetch a previous calculated number.
            number = cache.get(lastCachedNumber);   

            if (number == null)
                throw new IllegalStateException(String.format(
                        "this should be cached. n=%d, lastCachedNumber=%d", n,
                        lastCachedNumber));

            for (int i = lastCachedNumber + 1; i <= n; i++) {
                number = number + 1;
                cache.put(i, number);
                lastCachedNumber = i;
            }
        }

        unlock();

        return number;
    }

    private void lock() {
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void unlock() {
        semaphore.release();
    }

    public static void main(String[] args) {
        ConcurrentCache cachedObject = new ConcurrentCache();
        for (int nThreads = 0; nThreads < 5; nThreads++) {

            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int cacheValue = 0; cacheValue < 1000; cacheValue++) {

                        if (cachedObject.fetchAndCache(cacheValue) == null) {

                            throw new IllegalStateException(String.format(
                                    "the number %d should be cached",
                                    cacheValue));
                        }
                    }
                }
            }).start();

        }
    } 
}

谢谢你的帮助。

1 个答案:

答案 0 :(得分:1)

指点/想法很少:
1)在创建Map时预先调整大小,以适应所有/未来的缓存值,Map调整大小非常不安全且耗时 2)您可以将整个算法简化为

YourClass.get(int i) {
    if (!entryExists(i)) {
        lockEntry(i);
        entry = createEntry(i);
        putEntryInCache(i, entry);
        unlockEntry(i);
    }
    return entry;
}

修改
另一点:
3)你的缓存方法非常糟糕 - 想象如果第一个请求要获得@ 1,000,000位置的东西会发生什么? 在单独的线程中预先填充会更好......