在 Java 中同步 String 对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/133988/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 08:41:41  来源:igfitidea点击:

Synchronizing on String objects in Java

javamultithreadingsynchronizationthread-safetysynchronized

提问by matt b

I have a webapp that I am in the middle of doing some load/performance testing on, particularily on a feature where we expect a few hundred users to be accessing the same page and hitting refresh about every 10 seconds on this page. One area of improvement that we found we could make with this function was to cache the responses from the web service for some period of time, since the data is not changing.

我有一个 web 应用程序,我正在对它进行一些负载/性能测试,特别是在我们预计数百个用户访问同一页面并在此页面上每 10 秒刷新一次的功能上。我们发现可以使用此功能进行的一个改进领域是将来自 Web 服务的响应缓存一段时间,因为数据没有改变。

After implementing this basic caching, in some further testing I found out that I didn't consider how concurrent threads could access the Cache at the same time. I found that within the matter of ~100ms, about 50 threads were trying to fetch the object from the Cache, finding that it had expired, hitting the web service to fetch the data, and then putting the object back in the cache.

在实现了这个基本的缓存之后,在一些进一步的测试中我发现我没有考虑并发线程如何同时访问缓存。我发现在大约 100 毫秒内,大约有 50 个线程试图从缓存中获取对象,发现它已过期,点击 Web 服务获取数据,然后将对象放回缓存中。

The original code looked something like this:

原始代码如下所示:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {

  final String key = "Data-" + email;
  SomeData[] data = (SomeData[]) StaticCache.get(key);

  if (data == null) {
      data = service.getSomeDataForEmail(email);

      StaticCache.set(key, data, CACHE_TIME);
  }
  else {
      logger.debug("getSomeDataForEmail: using cached object");
  }

  return data;
}

So, to make sure that only one thread was calling the web service when the object at keyexpired, I thought I needed to synchronize the Cache get/set operation, and it seemed like using the cache key would be a good candidate for an object to synchronize on (this way, calls to this method for email [email protected] would not be blocked by method calls to [email protected]).

因此,为了确保在对象key过期时只有一个线程正在调用 Web 服务,我认为我需要同步 Cache get/set 操作,并且使用缓存键似乎是一个很好的候选对象同步(这样,对电子邮件 [email protected] 的调用将不会被对 [email protected] 的方法调用阻止)。

I updated the method to look like this:

我将方法更新为如下所示:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {


  SomeData[] data = null;
  final String key = "Data-" + email;

  synchronized(key) {      
    data =(SomeData[]) StaticCache.get(key);

    if (data == null) {
        data = service.getSomeDataForEmail(email);
        StaticCache.set(key, data, CACHE_TIME);
    }
    else {
      logger.debug("getSomeDataForEmail: using cached object");
    }
  }

  return data;
}

I also added logging lines for things like "before synchronization block", "inside synchronization block", "about to leave synchronization block", and "after synchronization block", so I could determine if I was effectively synchronizing the get/set operation.

我还为诸如“同步块之前”、“同步块内部”、“即将离开同步块”和“同步块之后”之类的内容添加了日志记录行,因此我可以确定我是否有效地同步了 get/set 操作。

However it doesn't seem like this has worked. My test logs have output like:

然而,这似乎并没有奏效。我的测试日志输出如下:

(log output is 'threadname' 'logger name' 'message')
http-80-Processor253 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor253 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor253 cache.StaticCache - get: object at key [[email protected]] has expired
http-80-Processor253 cache.StaticCache - get: key [[email protected]] returning value [null]
http-80-Processor263 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor263 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor263 cache.StaticCache - get: object at key [[email protected]] has expired
http-80-Processor263 cache.StaticCache - get: key [[email protected]] returning value [null]
http-80-Processor131 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor131 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor131 cache.StaticCache - get: object at key [[email protected]] has expired
http-80-Processor131 cache.StaticCache - get: key [[email protected]] returning value [null]
http-80-Processor104 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor104 cache.StaticCache - get: object at key [[email protected]] has expired
http-80-Processor104 cache.StaticCache - get: key [[email protected]] returning value [null]
http-80-Processor252 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor283 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor2 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor2 jsp.view-page - getSomeDataForEmail: inside synchronization block

(日志输出为 'threadname' 'logger name' 'message')
http-80-Processor253 jsp.view-page - getSomeDataForEmail:即将进入同步块
http-80-Processor253 jsp.view-page - getSomeDataForEmail:同步块内
http -80-Processor253 cache.StaticCache - get: object at key [[email protected]] 已过期
http-80-Processor253 cache.StaticCache - get: key [[email protected]] 返回值 [null]
http-80-Processor263 jsp.view-page - getSomeDataForEmail: 即将进入同步块
http-80-Processor263 jsp.view-page - getSomeDataForEmail: 同步块内
http-80-Processor263 cache.StaticCache - get: object at key [SomeData [email protected]] 已过期
http-80-Processor263 cache.StaticCache - get: key [[email protected]] 返回值 [null]
http-80-Processor131 jsp.view-page - getSomeDataForEmail: 即将进入同步块
http-80-Processor131 jsp .view-page - getSomeDataForEmail: 在同步块内
http-80-Processor131 cache.StaticCache - get: object at key [[email protected]] 已过期
http-80-Processor131 cache.StaticCache - get: key [SomeData- [email protected]] 返回值 [null]
http-80-Processor104 jsp.view-page - getSomeDataForEmail:内部同步块
http-80-Processor104 cache.StaticCache - get:对象在关键 [[email protected]]已过期
http-80-Processor104 cache.StaticCache - get: key [[email protected]] 返回值 [null]
http-80-Processor252 jsp.view-page - getSomeDataForEmail: 即将进入同步块
http-80-Processor283 jsp .view-page - getSomeDataForEmail:即将进入同步区块
http-80-Processor2 jsp.view-page - getSomeDataForEmail:即将进入同步区块
http-80-Processor2 jsp.view-page - getSomeDataForEmail:进入同步区块

I wanted to see only one thread at a time entering/exiting the synchronization block around the get/set operations.

我希望一次只看到一个线程围绕 get/set 操作进入/退出同步块。

Is there an issue in synchronizing on String objects? I thought the cache-key would be a good choice as it is unique to the operation, and even though the final String keyis declared within the method, I was thinking that each thread would be getting a reference to the same objectand therefore would synchronization on this single object.

在 String 对象上同步是否存在问题?我认为缓存键是一个不错的选择,因为它对操作来说是唯一的,即使final String key在方法中声明了,我认为每个线程都会获得对同一对象的引用,因此会对此进行同步单个对象。

What am I doing wrong here?

我在这里做错了什么?

Update: after looking further at the logs, it seems like methods with the same synchronization logic where the key is always the same, such as

更新:进一步查看日志后,似乎方法具有相同的同步逻辑,其中密钥始终相同,例如

final String key = "blah";
...
synchronized(key) { ...

do not exhibit the same concurrency problem - only one thread at a time is entering the block.

不要表现出相同的并发问题 - 一次只有一个线程进入块。

Update 2: Thanks to everyone for the help! I accepted the first answer about intern()ing Strings, which solved my initial problem - where multiple threads were entering synchronized blocks where I thought they shouldn't, because the key's had the same value.

更新2:感谢大家的帮助!我接受了关于intern()ing Strings的第一个答案,它解决了我最初的问题 - 多个线程正在进入我认为不应该进入的同步块,因为key's 具有相同的值。

As others have pointed out, using intern()for such a purpose and synchronizing on those Strings does indeed turn out to be a bad idea - when running JMeter tests against the webapp to simulate the expected load, I saw the used heap size grow to almost 1GB in just under 20 minutes.

正如其他人指出的那样,intern()用于这样的目的并在这些字符串上进行同步确实证明是一个坏主意 - 当对 web 应用程序运行 JMeter 测试以模拟预期负载时,我看到使用的堆大小增长到近 1GB不到20分钟。

Currently I'm using the simple solution of just synchronizing the entire method - but I reallylike the code samples provided by martinprobst and MBCook, but since I have about 7 similar getData()methods in this class currently (since it needs about 7 different pieces of data from a web service), I didn't want to add almost-duplicate logic about getting and releasing locks to each method. But this is definitely very, very valuable info for future usage. I think these are ultimately the correct answers on how best to make an operation like this thread-safe, and I'd give out more votes to these answers if I could!

目前我正在使用仅同步整个方法的简单解决方案 - 但我真的很喜欢 martinprobst 和 MBCook 提供的代码示例,但是因为我getData()目前在这个类中有大约 7 个类似的方法(因为它需要大约 7 个不同的数据片段来自 Web 服务),我不想为每个方法添加关于获取和释放锁的几乎重复的逻辑。但这对于未来的使用绝对是非常非常有价值的信息。我认为这些最终是关于如何最好地使这种线程安全的操作的正确答案,如果可以的话,我会为这些答案投更多票!

采纳答案by Steve Jessop

Without putting my brain fully into gear, from a quick scan of what you say it looks as though you need to intern() your Strings:

没有让我的大脑完全投入,从快速扫描你所说的看起来好像你需要实习()你的字符串:

final String firstkey = "Data-" + email;
final String key = firstkey.intern();

Two Strings with the same value are otherwise not necessarily the same object.

两个具有相同值的字符串不一定是同一个对象。

Note that this may introduce a new point of contention, since deep in the VM, intern() may have to acquire a lock. I have no idea what modern VMs look like in this area, but one hopes they are fiendishly optimised.

请注意,这可能会引入一个新的争用点,因为在 VM 深处,intern() 可能必须获取锁。我不知道这方面的现代虚拟机是什么样子,但有人希望它们能被彻底优化。

I assume you know that StaticCache still needs to be thread-safe. But the contention there should be tiny compared with what you'd have if you were locking on the cache rather than just the key while calling getSomeDataForEmail.

我假设您知道 StaticCache 仍然需要是线程安全的。但是,与在调用 getSomeDataForEmail 时锁定缓存而不仅仅是键时相比,那里的争用应该很小。

Response to question update:

回复问题更新

I think that's because a string literal always yields the same object. Dave Costa points out in a comment that it's even better than that: a literal always yields the canonical representation. So all String literals with the same value anywhere in the program would yield the same object.

我认为这是因为字符串文字总是产生相同的对象。Dave Costa 在评论中指出,它甚至比这更好:文字总是产生规范表示。因此,程序中任何地方具有相同值的所有字符串文字都会产生相同的对象。

Edit

编辑

Others have pointed out that synchronizing on intern strings is actually a really bad idea- partly because creating intern strings is permitted to cause them to exist in perpetuity, and partly because if more than one bit of code anywhere in your program synchronizes on intern strings, you have dependencies between those bits of code, and preventing deadlocks or other bugs may be impossible.

其他人指出,同步实习字符串实际上是一个非常糟糕的主意- 部分是因为允许创建实习字符串使它们永久存在,部分是因为如果程序中任何地方的多于一位代码在实习字符串上同步,您在这些代码位之间存在依赖性,并且可能无法防止死锁或其他错误。

Strategies to avoid this by storing a lock object per key string are being developed in other answers as I type.

在我输入的其他答案中,正在开发通过为每个键字符串存储一个锁对象来避免这种情况的策略。

Here's an alternative - it still uses a singular lock, but we know we're going to need one of those for the cache anyway, and you were talking about 50 threads, not 5000, so that may not be fatal. I'm also assuming that the performance bottleneck here is slow blocking I/O in DoSlowThing() which will therefore hugely benefit from not being serialised. If that's not the bottleneck, then:

这是另一种选择 - 它仍然使用单一锁,但我们知道无论如何我们都需要其中一个用于缓存,并且您说的是 50 个线程,而不是 5000 个,因此这可能不是致命的。我还假设这里的性能瓶颈是 DoSlowThing() 中的缓慢阻塞 I/O,因此这将大大受益于不被序列化。如果这不是瓶颈,那么:

  • If the CPU is busy then this approach may not be sufficient and you need another approach.
  • If the CPU is not busy, and access to server is not a bottleneck, then this approach is overkill, and you might as well forget both this and per-key locking, put a big synchronized(StaticCache) around the whole operation, and do it the easy way.
  • 如果 CPU 很忙,那么这种方法可能不够,您需要另一种方法。
  • 如果 CPU 不忙,访问服务器也不是瓶颈,那么这种做法就大材小用了,你不妨把这个和按键锁都忘了,在整个操作周围放一个大的 synchronized(StaticCache),然后做这是简单的方法。

Obviously this approach needs to be soak tested for scalability before use -- I guarantee nothing.

显然,这种方法在使用前需要对可扩展性进行浸泡测试——我不保证什么。

This code does NOT require that StaticCache is synchronized or otherwise thread-safe. That needs to be revisited if any other code (for example scheduled clean-up of old data) ever touches the cache.

此代码不需要 StaticCache 是同步的或线程安全的。如果任何其他代码(例如旧数据的预定清理)曾经接触过缓存,则需要重新访问。

IN_PROGRESS is a dummy value - not exactly clean, but the code's simple and it saves having two hashtables. It doesn't handle InterruptedException because I don't know what your app wants to do in that case. Also, if DoSlowThing() consistently fails for a given key this code as it stands is not exactly elegant, since every thread through will retry it. Since I don't know what the failure criteria are, and whether they are liable to be temporary or permanent, I don't handle this either, I just make sure threads don't block forever. In practice you may want to put a data value in the cache which indicates 'not available', perhaps with a reason, and a timeout for when to retry.

IN_PROGRESS 是一个虚拟值 - 不完全干净,但代码很简单,它节省了两个哈希表。它不处理 InterruptedException 因为我不知道在这种情况下您的应用程序想要做什么。此外,如果 DoSlowThing() 对于给定的键始终失败,则此代码并不完全优雅,因为每个线程都会重试。由于我不知道失败标准是什么,以及它们是临时的还是永久的,我也不处理这个问题,我只是确保线程不会永远阻塞。在实践中,您可能希望在缓存中放置一个指示“不可用”的数据值,这可能是有原因的,以及重试时间的超时。

// do not attempt double-check locking here. I mean it.
synchronized(StaticObject) {
    data = StaticCache.get(key);
    while (data == IN_PROGRESS) {
        // another thread is getting the data
        StaticObject.wait();
        data = StaticCache.get(key);
    }
    if (data == null) {
        // we must get the data
        StaticCache.put(key, IN_PROGRESS, TIME_MAX_VALUE);
    }
}
if (data == null) {
    // we must get the data
    try {
        data = server.DoSlowThing(key);
    } finally {
        synchronized(StaticObject) {
            // WARNING: failure here is fatal, and must be allowed to terminate
            // the app or else waiters will be left forever. Choose a suitable
            // collection type in which replacing the value for a key is guaranteed.
            StaticCache.put(key, data, CURRENT_TIME);
            StaticObject.notifyAll();
        }
    }
}

Every time anything is added to the cache, all threads wake up and check the cache (no matter what key they're after), so it's possible to get better performance with less contentious algorithms. However, much of that work will take place during your copious idle CPU time blocking on I/O, so it may not be a problem.

每次向缓存中添加任何内容时,所有线程都会唤醒并检查缓存(无论它们使用什么键),因此可以使用较少争议的算法获得更好的性能。但是,大部分工作将在大量空闲 CPU 时间阻塞 I/O 期间进行,因此这可能不是问题。

This code could be commoned-up for use with multiple caches, if you define suitable abstractions for the cache and its associated lock, the data it returns, the IN_PROGRESS dummy, and the slow operation to perform. Rolling the whole thing into a method on the cache might not be a bad idea.

如果您为缓存及其关联的锁、它返回的数据、IN_PROGRESS 虚拟和要执行的慢速操作定义了合适的抽象,则此代码可以与多个缓存一起使用。将整个事情滚动到缓存上的一个方法中可能不是一个坏主意。

回答by Mario Ortegón

The call:

电话:

   final String key = "Data-" + email;

creates a new object every time the method is called. Because that object is what you use to lock, and every call to this method creates a new object, then you are not really synchronizing access to the map based on the key.

每次调用该方法时都会创建一个新对象。因为该对象是您用来锁定的对象,并且每次调用此方法都会创建一个新对象,所以您并没有真正根据键同步对映射的访问。

This further explain your edit. When you have a static string, then it will work.

这进一步解释了您的编辑。当你有一个静态字符串时,它就会起作用。

Using intern() solves the problem, because it returns the string from an internal pool kept by the String class, that ensures that if two strings are equal, the one in the pool will be used. See

使用 intern() 解决了这个问题,因为它从 String 类保存的内部池中返回字符串,确保如果两个字符串相等,将使用池中的一个。看

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern()

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern()

回答by Alexander

Your main problem is not just that there might be multiple instances of String with the same value. The main problem is that you need to have only one monitor on which to synchronize for accessing the StaticCache object. Otherwise multiple threads might end up concurrently modifying StaticCache (albeit under different keys), which most likely doesn't support concurrent modification.

您的主要问题不仅仅是可能有多个具有相同值的 String 实例。主要问题是您只需要一个监视器来同步访问 StaticCache 对象。否则多个线程最终可能会同时修改 StaticCache(尽管在不同的键下),这很可能不支持并发修改。

回答by Martin Probst

Synchronizing on an intern'd String might not be a good idea at all - by interning it, the String turns into a global object, and if you synchronize on the same interned strings in different parts of your application, you might get really weird and basically undebuggable synchronization issues such as deadlocks. It might seem unlikely, but when it happens you are really screwed. As a general rule, only ever synchronize on a local object where you're absolutely sure that no code outside of your module might lock it.

同步一个实习字符串可能根本不是一个好主意——通过实习,字符串变成一个全局对象,如果你在应用程序的不同部分同步同一个实习字符串,你可能会变得非常奇怪和基本上无法调试的同步问题,例如死锁。这可能看起来不太可能,但当它发生时,你真的被搞砸了。作为一般规则,只在本地对象上进行同步,您绝对可以确定模块之外的任何代码都不会锁定它。

In your case, you can use a synchronized hashtable to store locking objects for your keys.

在您的情况下,您可以使用同步哈希表来存储密钥的锁定对象。

E.g.:

例如:

Object data = StaticCache.get(key, ...);
if (data == null) {
  Object lock = lockTable.get(key);
  if (lock == null) {
    // we're the only one looking for this
    lock = new Object();
    synchronized(lock) {
      lockTable.put(key, lock);
      // get stuff
      lockTable.remove(key);
    }
  } else {
    synchronized(lock) {
      // just to wait for the updater
    }
    data = StaticCache.get(key);
  }
} else {
  // use from cache
}

This code has a race condition, where two threads might put an object into the lock table after each other. This should however not be a problem, because then you only have one more thread calling the webservice and updating the cache, which shouldn't be a problem.

这段代码有一个竞争条件,两个线程可能会依次将一个对象放入锁表中。然而,这应该不是问题,因为这样你就只有一个线程调用 web 服务并更新缓存,这应该不是问题。

If you're invalidating the cache after some time, you should check whether data is null again after retrieving it from the cache, in the lock != null case.

如果您在一段时间后使缓存无效,您应该在从缓存中检索数据后再次检查数据是否为空,在 lock != null 的情况下。

Alternatively, and much easier, you can make the whole cache lookup method ("getSomeDataByEmail") synchronized. This will mean that all threads have to synchronize when they access the cache, which might be a performance problem. But as always, try this simple solution first and see if it's really a problem! In many cases it should not be, as you probably spend much more time processing the result than synchronizing.

或者,更容易的是,您可以使整个缓存查找方法(“getSomeDataByEmail”)同步。这意味着所有线程在访问缓存时都必须同步,这可能是一个性能问题。但一如既往,先尝试这个简单的解决方案,看看它是否真的有问题!在许多情况下不应该是这样,因为您处理结果的时间可能比同步要多得多。

回答by Matthias Winkelmann

Why not just render a static html page that gets served to the user and regenerated every x minutes?

为什么不只呈现一个静态的 html 页面,该页面会提供给用户并每 x 分钟重新生成一次?

回答by MBCook

Others have suggested interning the strings, and that will work.

其他人建议对字符串进行实习,这会奏效。

The problem is that Java has to keep interned strings around. I was told it does this even if you're not holding a reference because the value needs to be the same the next time someone uses that string. This means interning all the strings may start eating up memory, which with the load you're describing could be a big problem.

问题是 Java 必须保留内部字符串。有人告诉我,即使您没有持有引用,它也会这样做,因为下次有人使用该字符串时,该值必须相同。这意味着实习所有字符串可能会开始占用内存,这对于您描述的负载可能是一个大问题。

I have seen two solutions to this:

我已经看到了两种解决方案:

You could synchronize on another object

您可以在另一个对象上同步

Instead of the email, make an object that holds the email (say the User object) that holds the value of email as a variable. If you already have another object that represents the person (say you already pulled something from the DB based on their email) you could use that. By implementing the equals method and the hashcode method you can make sure Java considers the objects the same when you do a static cache.contains() to find out if the data is already in the cache (you'll have to synchronize on the cache).

而不是电子邮件,创建一个保存电子邮件的对象(比如用户对象),将电子邮件的值作为变量保存。如果你已经有另一个代表这个人的对象(假设你已经根据他们的电子邮件从数据库中提取了一些东西),你可以使用它。通过实现 equals 方法和 hashcode 方法,您可以确保 Java 在执行静态 cache.contains() 以查明数据是否已在缓存中时将对象视为相同(您必须在缓存上进行同步) )。

Actually, you could keep a second Map for objects to lock on. Something like this:

实际上,您可以为要锁定的对象保留第二个 Map。像这样的东西:

Map<String, Object> emailLocks = new HashMap<String, Object>();

Object lock = null;

synchronized (emailLocks) {
    lock = emailLocks.get(emailAddress);

    if (lock == null) {
        lock = new Object();
        emailLocks.put(emailAddress, lock);
    }
}

synchronized (lock) {
    // See if this email is in the cache
    // If so, serve that
    // If not, generate the data

    // Since each of this person's threads synchronizes on this, they won't run
    // over eachother. Since this lock is only for this person, it won't effect
    // other people. The other synchronized block (on emailLocks) is small enough
    // it shouldn't cause a performance problem.
}

This will prevent 15 fetches on the same email address at one. You'll need something to prevent too many entries from ending up in the emailLocks map. Using LRUMaps from Apache Commons would do it.

这将防止在同一电子邮件地址上同时进行 15 次提取。您需要一些东西来防止过多的条目出现在 emailLocks 映射中。使用来自 Apache Commons 的LRUMap就可以了。

This will need some tweaking, but it may solve your problem.

这将需要一些调整,但它可能会解决您的问题。

Use a different key

使用不同的键

If you are willing to put up with possible errors (I don't know how important this is) you could use the hashcode of the String as the key. ints don't need to be interned.

如果您愿意忍受可能的错误(我不知道这有多重要),您可以使用 String 的哈希码作为键。ints 不需要实习。

Summary

概括

I hope this helps. Threading is fun, isn't it? You could also use the session to set a value meaning "I'm already working on finding this" and check that to see if the second (third, Nth) thread needs to attempt to create the or just wait for the result to show up in the cache. I guess I had three suggestions.

我希望这有帮助。线程很有趣,不是吗?您还可以使用会话设置一个值,意思是“我已经在寻找这个”并检查第二个(第三个,第 N 个)线程是否需要尝试创建或等待结果出现在缓存中。我想我有三个建议。

回答by McDowell

Strings are notgood candidates for synchronization. If you must synchronize on a String ID, it can be done by using the string to create a mutex (see "synchronizing on an ID"). Whether the cost of that algorithm is worth it depends on whether invoking your service involves any significant I/O.

字符串不是同步的好候选。如果您必须在字符串 ID 上进行同步,可以通过使用字符串创建互斥锁来完成(请参阅“在 ID 上同步”)。该算法的成本是否值得取决于调用您的服务是否涉及任何重要的 I/O。

Also:

还:

  • I hope the StaticCache.get()and set()methods are threadsafe.
  • String.intern()comes at a cost (one that varies between VM implementations) and should be used with care.
  • 我希望StaticCache.get()set()方法是线程安全的。
  • String.intern()是有代价的(在 VM 实现之间会有所不同),应谨慎使用。

回答by John Gardner

I'd also suggest getting rid of the string concatenation entirely if you don't need it.

如果您不需要它,我还建议完全摆脱字符串连接。

final String key = "Data-" + email;

Is there other things/types of objects in the cache that use the email address that you need that extra "Data-" at the beginning of the key?

缓存中是否还有其他东西/类型的对象使用电子邮件地址,您需要在密钥开头的额外“数据-”?

if not, i'd just make that

如果没有,我就这样做

final String key = email;

and you avoid all that extra string creation too.

并且您也避免了所有额外的字符串创建。

回答by oxbow_lakes

You can use the 1.5 concurrency utilities to provide a cache designed to allow multiple concurrent access, and a single point of addition (i.e. only one thread ever performing the expensive object "creation"):

您可以使用 1.5 并发实用程序来提供一个缓存,该缓存旨在允许多个并发访问和一个添加点(即只有一个线程执行昂贵的对象“创建”):

 private ConcurrentMap<String, Future<SomeData[]> cache;
 private SomeData[] getSomeDataByEmail(final WebServiceInterface service, final String email) throws Exception {

  final String key = "Data-" + email;
  Callable<SomeData[]> call = new Callable<SomeData[]>() {
      public SomeData[] call() {
          return service.getSomeDataForEmail(email);
      }
  }
  FutureTask<SomeData[]> ft; ;
  Future<SomeData[]> f = cache.putIfAbsent(key, ft= new FutureTask<SomeData[]>(call)); //atomic
  if (f == null) { //this means that the cache had no mapping for the key
      f = ft;
      ft.run();
  }
  return f.get(); //wait on the result being available if it is being calculated in another thread
}

Obviously, this doesn't handle exceptions as you'd want to, and the cache doesn't have eviction built in. Perhaps you could use it as a basis to change your StaticCache class, though.

显然,这不会像您希望的那样处理异常,并且缓存没有内置驱逐功能。不过,也许您可​​以将其用作更改 StaticCache 类的基础。

回答by kohlerm

Use a decent caching framework such as ehcache.

使用一个不错的缓存框架,例如ehcache

Implementing a good cache is not as easy as some people believe.

实现一个好的缓存并不像某些人认为的那么容易。

Regarding the comment that String.intern() is a source of memory leaks, that is actually not true. Interned Strings aregarbage collected,it just might take longer because on certain JVM'S (SUN) they are stored in Perm space which is only touched by full GC's.

关于 String.intern() 是内存泄漏源的评论,实际上并非如此。实习字符串垃圾收集的,它可能需要更长的时间,因为在某些 JVM (SUN) 上,它们存储在 Perm 空间中,只有完整的 GC 才会触及。