当前位置: 首页 > 面试题库 >

Java中一个对象的线程安全缓存

黄宏旷
2023-03-14
问题内容

假设我们的应用程序中有一个CountryList对象,该对象应返回国家/地区列表。国家/地区的加载是一项繁重的操作,因此应将列表缓存。

其他要求:

  • CountryList应该是线程安全的
  • CountryList应该延迟加载(仅按需加载)
  • CountryList应该支持缓存无效
  • 考虑到极少数情况下会使缓存无效,应该优化CountryList

我想出了以下解决方案:

public class CountryList {
    private static final Object ONE = new Integer(1);

    // MapMaker is from Google Collections Library    
    private Map<Object, List<String>> cache = new MapMaker()
        .initialCapacity(1)
        .makeComputingMap(
            new Function<Object, List<String>>() {
                @Override
                public List<String> apply(Object from) {
                    return loadCountryList();
                }
            });

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

    public List<String> list() {
        return cache.get(ONE);
    }

    public void invalidateCache() {
        cache.remove(ONE);
    }
}

你怎么看待这件事?你看到不好的东西吗?还有其他方法吗?我怎样才能使它更好?在这种情况下,我是否应该寻找另一种解决方案?

谢谢。


问题答案:

谢谢大家 ,尤其是对提出这个想法的用户“ gid ”。

我的目标是考虑到invalidate()操作将被称为非常罕见,从而优化get()操作的性能。

我编写了一个测试类,该类启动了16个线程,每个线程调用get()-Operation一百万次。在本课程中,我介绍了我的2核机器的一些实现。

测试结果

Implementation              Time
no synchronisation          0,6 sec
normal synchronisation      7,5 sec
with MapMaker               26,3 sec
with Suppliers.memoize      8,2 sec
with optimized memoize      1,5 sec

1)“不同步”不是线程安全的,但它为我们提供了可以与之相比的最佳性能。

@Override
public List<String> list() {
    if (cache == null) {
        cache = loadCountryList();
    }
    return cache;
}

@Override
public void invalidateCache() {
    cache = null;
}

2)“正常同步”-相当不错的性能,标准的轻松实现

@Override
public synchronized List<String> list() {
    if (cache == null) {
        cache = loadCountryList();
    }
    return cache;
}

@Override
public synchronized void invalidateCache() {
    cache = null;
}

3)“使用MapMaker”-性能非常差。

有关代码,请参见顶部的问题。

4)“与Suppliers.memoize”-良好的表现。但是,对于性能相同的“常规同步”,我们需要对其进行优化,或者仅使用“常规同步”。

有关代码,请参见用户“ gid”的答案。

5) “具有优化 的备忘 -与“不同步”实现相当的性能,但具有线程安全性。这是我们需要的。

缓存类本身:(此处使用的Supplier接口来自Google Collections
Library,它只有一种get()方法。请参见http://google-
collections.googlecode.com/svn/trunk/javadoc/com/google/ common / base /
Supplier.html


public class LazyCache<T> implements Supplier<T> {
    private final Supplier<T> supplier;

    private volatile Supplier<T> cache;

    public LazyCache(Supplier<T> supplier) {
        this.supplier = supplier;
        reset();
    }

    private void reset() {
        cache = new MemoizingSupplier<T>(supplier);
    }

    @Override
    public T get() {
        return cache.get();
    }

    public void invalidate() {
        reset();
    }

    private static class MemoizingSupplier<T> implements Supplier<T> {
        final Supplier<T> delegate;
        volatile T value;

        MemoizingSupplier(Supplier<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public T get() {
            if (value == null) {
                synchronized (this) {
                    if (value == null) {
                        value = delegate.get();
                    }
                }
            }
            return value;
        }
    }
}

使用示例:

public class BetterMemoizeCountryList implements ICountryList {

    LazyCache<List<String>> cache = new LazyCache<List<String>>(new Supplier<List<String>>(){
        @Override
        public List<String> get() {
            return loadCountryList();
        }
    });

    @Override
    public List<String> list(){
        return cache.get();
    }

    @Override
    public void invalidateCache(){
        cache.invalidate();
    }

    private List<String> loadCountryList() {
        // this should normally load a full list from the database,
        // but just for this instance we mock it with:
        return Arrays.asList("Germany", "Russia", "China");
    }
}


 类似资料:
  • 我正在用本机线程(pthreads)编写一个C应用程序,我需要调用一些Java方法等。我不确定哪些JNI对象可以安全地缓存在我的C对象中,以便以后使用,可能/可能由另一个线程使用。我确实知道,如果我的类的方法可以被不同的线程调用,我不能缓存JNIEnv,而是缓存JavaVM并通过附加当前线程获得JNIEnv。但这是否也意味着我无法缓存从JNIEnv获得的任何内容?我需要使用通过以下JNIEnv方法

  • 问题内容: 考虑几个并行运行的Web服务器实例。每个服务器都有对单个共享“状态保持器”的引用,该角色的作用是保留来自所有服务器的最新请求。 例如(): 在任何时间点,都可以从监视应用程序中调用“状态保持器”,该应用程序读取了最近的SLA报告请求。 在Java中实现这种生产者-消费者方案的最佳方法是什么,使Web服务器具有比SLA报告更高的优先级? CircularFifoBuffer似乎是容纳请求

  • 问题内容: Spring对象是线程安全的吗?如果没有,如何使它们线程安全? 问题答案: 这是两个不相关的问题: spring线程安全吗? 没有。 Spring具有不同的bean 作用域(例如Prototype,Singleton等),但是所有这些作用域都是在创建bean 时强制执行的。例如,每次“注入”一个“原型”范围的bean都会被创建,而一个“单个”范围的bean将被创建一次并在应用程序上下文

  • 我找到了关于线程安全的代码,但它没有来自给出示例的人的任何解释。我想知道为什么如果我不在“count”之前设置“synchronized”变量,那么count值将是非原子的(总是=200是期望的结果)。谢谢

  • 问题内容: 阅读“实践中的Java并发性”,第3.5节包含以下内容: 除了创建两个的明显的线程安全隐患外,该书还声称可能会发生发布问题。 此外,对于诸如 一个可以扔! 这怎么可能?我能想到的唯一允许这种荒谬行为的方法是,如果构造函数不被阻塞,那么当构造函数代码仍在另一个线程中运行时,将创建对实例的引用。 这可能吗? 问题答案: 之所以可行,是因为Java的内存模型较弱。它不保证读写顺序。 可以通过