Java缓存对象返回旧值

8
我有一个缓存列表,我的代码看起来像这样:

我有一个缓存列表,我的代码看起来像这样

public class MyList {
private final List<String> cache = new ArrayList<String>();

private List<String> loadMyList() {
    // HEAVY OPERATION TO LOAD DATA
}

public List<String> list() {
    synchronized (cache) {
        if( cache.size() == 0 ) {
            cache.addAll(loadMyList());
        }
        return Collections.unmodifiableList(cache);
    }
}

public void invalidateCache() {
    synchronized (cache) {
        cache.clear();
    }
}
}

由于列表加载非常繁重,我收到了一个请求:如果列表加载当前正在进行中,则返回“旧”的缓存数据...这种情况是否可能发生?有没有人能给出我在这方面如何操作的指导?
编辑:Adam Horvath和bayou.io建议像下面这样处理。
public class MyList 
{
private final List<String> cache = new ArrayList<String>();
private final List<String> oldCache = new ArrayList<String>();
private volatile boolean loadInProgress = false;

private List<String> loadMyList()
{
    // HEAVY OPERATION TO LOAD DATA
}

public List<String> list()
{
    synchronized (cache)
    {
        if( loadInProgress )
            return Collections.unmodifiableList( oldCache );
        else
            return Collections.unmodifiableList(cache);
    }
}

public void invalidateCache()
{
    synchronized (cache)
    {
        // copy to old cache
        oldCache = new ArrayList<String>( cache );
        // set flag that load is in progress
        loadInProgress = true;
        // clear cache
        cache.clear();

        // initialize load in new thread
        Thread t = new Thread(new Runnable()
        {
            public void run()
            {
                cache.addAll( loadMyList() );
                // set flag that load is finished
                loadInProgress = false;
            }
       });  
       t.start();


    }

   }
}

这个修改后的代码会有什么问题吗?由于我对多线程和/或缓存优化不熟悉,因此我希望得到所有性能建议。


5
如果我没有理解错误,你可能正在寻找CopyOnWriteArrayList - Pshemo
我认为你的意思是“缓存”,而不是“兑现”。 - Peter Lawrey
在你的例子中,调用 list() 的人会得到你列表的视图 - 这意味着当你清空/重新填充列表时,仍然拥有该视图副本的调用者可能会看到列表处于不稳定状态。你确定你想要这样吗?此外,在你的例子中没有“旧”数据 - 列表要么为空,要么有一些内容,但一旦加载完成,就不会再更新了... - assylias
你试图解决的实现方案有什么问题? - Peter Lawrey
1
如果第一个读取器在失效后负责加载数据,它将阻塞读取器的线程。这可能是不可取的;我们希望所有读取器都能快速返回。因此最好立即触发加载来使失效生效,可能在新线程中进行。如果在加载仍在进行时出现新的失效,则必须安排另一个加载,并确保第二个结果稍后被缓存。 - ZhongYu
显示剩余5条评论
1个回答

2
"由于列表加载很耗费资源,我收到了一个请求,如果当前正在进行列表加载,则返回“旧”的缓存数据...。"
由于您的“synchronized(cache)”块,这将不会发生。您需要一个易失性布尔标志(互斥锁),以告诉您正在生成列表。当线程尝试获取list()并且mutex为true时,它将接收Cached one。当loadMyList()完成时,将其设置为false。
因此,请删除同步块,并在单独的线程中开始加载列表。"
public class MyList {
    private List<String> cache = new ArrayList<String>();
    private volatile boolean loadInProgress = false;

    private List<String> loadMyList() {
        // HEAVY OPERATION TO LOAD DATA
    }

    public List<String> list() {
        // Whatever is in cache, you can always return it
        return Collections.unmodifiableList(cache);
    }

    /**
     * Starts the loader-thread and then continues.
     */
    public void invalidateCache() {
        // Next two lines make sure only one Loader-thread can be started at the same time
        synchronized (cache) {
            if (!loadInProgress) {
                // initialize load in new thread
                Thread t = new Thread("Loader-thread") {
                    @Override
                    public void run() {
                        List<String> toAssign = loadMyList();
                        // You can simply assign instead of copying
                        cache = toAssign;
                        // cache now "points to" refreshed list
                        loadInProgress = false;
                    }
                };
                loadInProgress = true;
                t.start();
                // Now let's exit the synchronized block. Hopefully the Thread will start working soon
            } else {
                // A Thread is already working or about to start working, don't bother him
            }
        }
    }
}

一个单独的状态标志似乎是一个糟糕的设计选择。有很多选项不需要这个标志。(例如,请参见Pshemos上面的评论) - Keppil
@Keppil 好主意!但我觉得他可以通过修复自己的实现来学习线程。 - Adam Horvath
@AdamHorvath 我编辑了我的问题,你有类似的想法吗? - John
这个版本具有非阻塞方法:list() 立即返回 "something",invalidateCache() 不会使其调用线程停止长时间的列表构建操作。还确保只有一个线程可以同时执行 loadMyList()。 - Adam Horvath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接