Java中的多线程对象→对象缓存映射?

发布于 2024-08-23 08:31:56 字数 465 浏览 3 评论 0 原文

我想要一个 Java 集合,它:

  • 将任意 Object 映射到 Object(不是 String 或仅受其他限制的键)
  • 将用作缓存;如果键不在缓存中,则将计算一个值(不必将其内置到集合中)
  • 将从多个线程同时访问
  • 将永远不会从其中删除项目
  • 必须非常 高效读取(缓存命中);写入不一定高效(缓存未命中)

如果多个线程中同时发生缓存未命中导致冗余计算,那也没关系;典型的情况是缓存一开始大部分被一个线程填满。

围绕线程不安全哈希表的同步块不符合高效读取标准。线程本地缓存很简单,但意味着新线程的成本很高,因为它们拥有缓存的完整副本。

Java 1.5 内置程序或我们可以复制到 MIT 许可项目中的一两个类文件是首选,而不是大型外部库。

I want a collection in Java which:

  • maps arbitrary Objects to Objects (not String or otherwise restricted keys only)
  • will be used as a cache; if the key is not in the cache, a value will be computed (this doesn't have to be built into the collection)
  • will be accessed from multiple threads simultaneously
  • will never have items removed from it
  • must be very efficient to read (cache hit); not necessarily efficient to write (cache miss)

It's OK if cache misses simultaneously in multiple threads cause redundant computations; the typical case is that the cache is mostly filled by one thread at first.

A synchronized block around a thread-unsafe hashtable fails the efficient-to-read criterion. Thread-local caches would be straightforward but mean that new threads are expensive since they have full copies of the cache.

Java 1.5 built-ins, or one-or-few class files we can copy into our MIT-licensed project, are preferred, rather than large external libraries.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

情泪▽动烟 2024-08-30 08:31:56

使用 java 并发

ConcurrentHashMap<object, object> table;

public object getFromCache(object key)
{
    value = table.get(key);

    if (value == null)
    {
        //key isn't a key into this table, ie. it's not in the cache
        value = calculateValueForKey(key)
        object fromCache = table.putIfAbsent(key, value);
    }

    return value;
}

/**
* This calculates a new value to put into the cache
*/
public abstract object calculateValueForKey(object key);

hashmap这不再是多线程缓存的通用解决方案,因为它依赖于对象不可变的事实,因此对象等效性并不重要。

Use the java concurrent hashmap

ConcurrentHashMap<object, object> table;

public object getFromCache(object key)
{
    value = table.get(key);

    if (value == null)
    {
        //key isn't a key into this table, ie. it's not in the cache
        value = calculateValueForKey(key)
        object fromCache = table.putIfAbsent(key, value);
    }

    return value;
}

/**
* This calculates a new value to put into the cache
*/
public abstract object calculateValueForKey(object key);

N.b. This is not longer a general solution for multithreaded caching, since it relies on the stated fact that objects are immutable, and thus object equivalence is not important.

云柯 2024-08-30 08:31:56

这是我自己的解决方案想法,但我不是线程编程方面的专家,所以请在您认为合适的情况下评论/投票/与其他答案进行比较。

使用线程局部变量 (java.lang.ThreadLocal),其中包含用作一级缓存的每线程哈希表。如果在此表中找不到该键,则会对二级缓存进行同步访问,这是一个由所有线程共享的同步访问哈希表。这样,缓存值的计算只进行一次,并且在所有线程之间共享,但每个线程都有一个从键到值的映射的本地副本,因此存在一些内存成本(但小于独立的每线程缓存),但读取效率很高。

This is my own idea for a solution, but I'm not an expert on threaded programming, so please comment/vote/compare to other answers as you feel appropriate.

Use a thread-local variable (java.lang.ThreadLocal) which contains a per-thread hashtable used as a first-level cache. If the key is not found in this table, synchronized access is done to a second-level cache, which is a synchronized-access hashtable shared by all threads. In this way, the calculation of the cache value is only ever done once, and it is shared among all threads, but each thread has a local copy of the mapping from keys to values, so there is some memory cost (but less than having independent per-thread caches), but reads are efficient.

拧巴小姐 2024-08-30 08:31:56

像这样的东西怎么样 SingletonCache 类?

public abstract class SingletonCache<K, V> {

    private final ConcurrentMap<K, V> cache = new ConcurrentHashMap<K, V>();

    public V get(K key) {
        V value = cache.get(key);
        if (value == null) {
            cache.putIfAbsent(key, newInstance(key));
            value = cache.get(key);
        }
        return value;
    }

    protected abstract V newInstance(K key);
}

要使用它,您可以扩展它并实现 newInstance 方法,该方法在缓存未命中的情况下创建一个新值。然后使用键调用 get 方法来获取与该键对应的实例。 这里是一个如何使用它的示例。

该类保证对于每个键,仅返回一个实例,但 newInstance 方法可能会被多次调用,在这种情况下,将使用第一个计算实例,其余实例将被丢弃。另请注意,此缓存不会删除旧实例,而是无限期地存储所有值(在我的用例中,必须缓存的实例数量有限)。从 ConcurrentHashMap 读取不会使用锁定,所以它应该满足您的效率要求。

How about something like this SingletonCache class from one of my projects?

public abstract class SingletonCache<K, V> {

    private final ConcurrentMap<K, V> cache = new ConcurrentHashMap<K, V>();

    public V get(K key) {
        V value = cache.get(key);
        if (value == null) {
            cache.putIfAbsent(key, newInstance(key));
            value = cache.get(key);
        }
        return value;
    }

    protected abstract V newInstance(K key);
}

To use it, you extend it and implement the newInstance method which creates a new value in case of a cache miss. Then call the get method with a key to get an instance which corresponds that key. Here is an example of how it is used.

That class guarantees that for each key, only one instance is returned, but the newInstance method may be called multiple times, in which case the first computed instance is used and the rest are discarded. Also note that this cache does not remove old instances, but stores all values indefinitely (in my use case there is a limited number of instances that must be cached). Reading from a ConcurrentHashMap does not use locking, so it should satisfy your efficiency requirement.

演出会有结束 2024-08-30 08:31:56

并发实践中的 5.6 节中描述的缓存怎么样,作者:布莱恩·戈茨此处对此进行了概述。

它仅使用 java.util.concurrent 包。

链接的文章建立了一个缓存并描述了每个版本的弱点,直到最终版本是一个高效的缓存,其中只有一个并发线程将计算不存在的条目。

我已经剪切并粘贴了下面的最终代码,但值得通读本文并思考概述的问题。或者甚至更好 - 买这本书 - 太棒了。

import java.util.concurrent.*;

public class Memoizer<A, V> implements Computable<A, V> {
  private final ConcurrentMap<A, Future<V>> cache
      = new ConcurrentHashMap<A, Future<V>>();
  private final Computable<A, V> c;
  public Memoizer(Computable<A, V> c) { this.c = c; }
  public V compute(final A arg) throws InterruptedException {
      while (true) {
          Future<V> f = cache.get(arg);
          if (f == null) {
              Callable<V> eval = new Callable<V>() {
                  public V call() throws InterruptedException {
                      return c.compute(arg);
                  } 
              };
              FutureTask<V> ft = new FutureTask<V>(eval);
              f = cache.putIfAbsent(arg, ft);
              if (f == null) {
                  f = ft;
                  ft.run();
              }
          }
          try {
              return f.get();
          } catch (CancellationException e) {
              cache.remove(arg, f);
          } catch (ExecutionException e) {
          // Kabutz: this is my addition to the code...
          try {
             throw e.getCause();
          } catch (RuntimeException ex) {
              throw ex;
          } catch (Error ex) {
              throw ex;
          } catch (Throwable t) {
              throw new IllegalStateException("Not unchecked", t);
          }
        }
     }
  }
}

How about the cache described in section 5.6 of Concurrency In Practice, by Brian Goetz? It's outlined here.

It only uses classes from the java.util.concurrent package.

The linked article builds up a cache and describes the weaknesses of each version, until final version is an efficient cache in which only one concurrent thread will compute an absent entry.

I've cut and pasted the final code below, but its worth reading through the article and thinking about the issues outlined. Or even better - buy the book - it's great.

import java.util.concurrent.*;

public class Memoizer<A, V> implements Computable<A, V> {
  private final ConcurrentMap<A, Future<V>> cache
      = new ConcurrentHashMap<A, Future<V>>();
  private final Computable<A, V> c;
  public Memoizer(Computable<A, V> c) { this.c = c; }
  public V compute(final A arg) throws InterruptedException {
      while (true) {
          Future<V> f = cache.get(arg);
          if (f == null) {
              Callable<V> eval = new Callable<V>() {
                  public V call() throws InterruptedException {
                      return c.compute(arg);
                  } 
              };
              FutureTask<V> ft = new FutureTask<V>(eval);
              f = cache.putIfAbsent(arg, ft);
              if (f == null) {
                  f = ft;
                  ft.run();
              }
          }
          try {
              return f.get();
          } catch (CancellationException e) {
              cache.remove(arg, f);
          } catch (ExecutionException e) {
          // Kabutz: this is my addition to the code...
          try {
             throw e.getCause();
          } catch (RuntimeException ex) {
              throw ex;
          } catch (Error ex) {
              throw ex;
          } catch (Throwable t) {
              throw new IllegalStateException("Not unchecked", t);
          }
        }
     }
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文