这个缓存线程安全吗?

时间:2014-09-14 16:20:21

标签: java multithreading caching guava

我很好奇这个缓存的想法是否有效:

@RequiredArgsConstructor @Getter class CacheEntry {
    static CacheEntry get(String string, int start) {
        int hash = string.hashCode() ^ start; // or something better
        int index = hash & (cache.length-1);
        CacheEntry result = cache[index];
        if (result!=null && result.matches(string, start)) return result;
        result = new CacheEntry(string, start, computeSomething(string, start));
        cache[index] = result;
        return result;
    }

    private boolean matches(String string, int start) {
        if (string.equals(this.string)) return false;
        if (start == this.start) return false;
        return true;
    }

    private static ImmutableSomething computeSomething(String string, int start) {
        ...
    }

    private static final CacheEntry[] cache = new CacheEntry[256];

    private final String string;
    private final int start;

    private final ImmutableSomething something;
}

注释来自lombok。试想一下每个人的名字都是如此。

目标是将呼叫保存到computeSomething并最小化分配。

缓存既不是同步的,也不是本地的线程。我们无法保证一个线程会看到另一个线程完成的更新。这是可以接受的。也没有任何保证,一个线程不会覆盖另一个线程的条目。这也是可以接受的。

在我写的一个小基准测试中,与sane caching alternatives相比,它带来了很好的加速。我关心的是正确性:可能会发生一个线程看到无效条目(例如,一个包含错误something的条目)?

1 个答案:

答案 0 :(得分:3)

只要CacheEntry是一个正确的不可变对象(而不仅仅是一个有效的不可变对象),这就可以工作。这是因为可以安全地发布不可变对象而不进行同步,并且对象引用赋值是原子的。

换句话说,如果CacheEntry不是完全不可变的,那么它是不安全的,因为消费者线程可能会看到一个不完全构造的对象。此外,如果缓存的是原始类型,其分配不是原子的(doublelong),那么使用者线程可能会看到垃圾(半分配的值)。

修改
根据{{​​3}},如果符合以下条件,则可以安全地发布对象而不进行同步:

  • 施工后无法修改其状态
  • 所有字段都声明为final
  • 构造正确(this关键字在构建期间不会转义)